diff --git a/.gitignore b/.gitignore index e5ba291004..568e9f2454 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ coverage.txt remoting/zookeeper/zookeeper-4unittest/ config_center/zookeeper/zookeeper-4unittest/ registry/zookeeper/zookeeper-4unittest/ -registry/consul/agent* \ No newline at end of file +registry/consul/agent* +config_center/apollo/mockDubbog.properties.json diff --git a/.travis.yml b/.travis.yml index 707e644814..7f30febe7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,11 @@ language: go +os: + - linux + - osx + go: - - "1.12" + - "1.13" env: - GO111MODULE=on @@ -10,10 +14,7 @@ install: true script: - go fmt ./... && [[ -z `git status -s` ]] - - mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar - - wget -P "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar - - cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ - - cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ + - chmod u+x before_ut.sh && ./before_ut.sh - go mod vendor && go test ./... -coverprofile=coverage.txt -covermode=atomic after_success: diff --git a/CHANGE.md b/CHANGE.md index 947a695ca8..ea2fe351cf 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,64 +1,98 @@ # Release Notes +--- + +## 1.3.0 + +### New Features + +- [Add apollo config center support](https://github.com/apache/dubbo-go/pull/250) +- [Gracefully shutdown](https://github.com/apache/dubbo-go/pull/255) +- [Add consistent hash load balance support](https://github.com/apache/dubbo-go/pull/261) +- [Add sticky connection support](https://github.com/apache/dubbo-go/pull/270) +- [Add async call for dubbo protocol](https://github.com/apache/dubbo-go/pull/272) +- [Add generic implement](https://github.com/apache/dubbo-go/pull/291) +- [Add request timeout for method](https://github.com/apache/dubbo-go/pull/284) +- [Add grpc protocol](https://github.com/apache/dubbo-go/pull/311) + +### Enhancement + +- [The SIGSYS and SIGSTOP are not supported in windows platform](https://github.com/apache/dubbo-go/pull/262) +- [Error should be returned when `NewURL` failed](https://github.com/apache/dubbo-go/pull/266) +- [Split config center GetConfig method](https://github.com/apache/dubbo-go/pull/267) +- [Modify closing method for dubbo protocol](https://github.com/apache/dubbo-go/pull/268) +- [Add SetLoggerLevel method](https://github.com/apache/dubbo-go/pull/271) +- [Change the position of the lock](https://github.com/apache/dubbo-go/pull/286) +- [Change zk version and add base_registry](https://github.com/apache/dubbo-go/pull/355) + +### Bugfixes + +- [Fix negative wait group count](https://github.com/apache/dubbo-go/pull/253) +- [After disconnection with ZK registry, cosumer can't listen to provider changes](https://github.com/apache/dubbo-go/pull/258) +- [The generic filter and default reference filters lack ','](https://github.com/apache/dubbo-go/pull/260) +- [Url encode zkpath](https://github.com/apache/dubbo-go/pull/283) +- [Fix jsonrpc about HTTP/1.1](https://github.com/apache/dubbo-go/pull/327) +- [Fix zk bug](https://github.com/apache/dubbo-go/pull/346) +- [HessianCodec failed to check package header length](https://github.com/apache/dubbo-go/pull/381) ## 1.2.0 ### New Features -- Add etcdv3 registry support -- Add nacos registry support -- Add fail fast cluster support -- Add available cluster support -- Add broadcast cluster support -- Add forking cluster support -- Add service token authorization support -- Add accessLog filter support -- Add tps limit support -- Add execute limit support -- Move callService to invoker & support attachments -- Move example in dubbo-go project away -- Support dynamic config center which compatible with dubbo 2.6.x & 2.7.x and commit the zookeeper impl +- [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 -- Remove client from pool before closing it -- Enhance the logic for fetching the local address -- Add protocol_conf default values -- Add task pool for getty -- Update getty: remove read queue -- Clean heartbeat from PendingResponse +- [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 -- Fix failover cluster bug and url parameter retries change int to string type -- Fix url params unsafe map -- Read protocol config by map key in config yaml instead of protocol name -- Fix dubbo group issues #238/ -- Fix bug in reference_config -- Fix high memory bug in zookeeper listener +- [GettyRPCClientPool remove deadlock](https://github.com/apache/dubbo-go/pull/183/files) +- [Fix failover cluster bug and url parameter retries change int to string type](https://github.com/apache/dubbo-go/pull/195) +- [Fix url params unsafe map](https://github.com/apache/dubbo-go/pull/201) +- [Read protocol config by map key in config yaml instead of protocol name](https://github.com/apache/dubbo-go/pull/218) +- *Fix dubbo group issues #238* [pr #243](https://github.com/apache/dubbo-go/pull/243) and [pr #244](https://github.com/apache/dubbo-go/pull/244) +- [Fix bug in reference_config](https://github.com/apache/dubbo-go/pull/157) +- [Fix high memory bug in zookeeper listener](https://github.com/apache/dubbo-go/pull/168) ## 1.1.0 ### New Features -- Support Java bigdecimal; -- Support all JDK exceptions; -- Support multi-version of service; -- Allow user set custom params for registry; -- Support zookeeper config center; -- Failsafe/Failback Cluster Strategy; +- [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 ; +- [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; -- Delete ineffassign; -- Add wg.Done() for mockDataListener; -- Delete wrong spelling words; -- Use sync.Map to defeat from gettyClientPool deadlock; -- Handle panic when function args list is empty; -- url.Values is not safe map; +- [Preventing dead loop when got zookeeper unregister event](https://github.com/apache/dubbo-go/pull/129) +- [Delete ineffassign](https://github.com/apache/dubbo-go/pull/127) +- [Add wg.Done() for mockDataListener](https://github.com/apache/dubbo-go/pull/118) +- [Delete wrong spelling words](https://github.com/apache/dubbo-go/pull/107) +- [Use sync.Map to defeat from gettyClientPool deadlock](https://github.com/apache/dubbo-go/pull/106) +- [Handle panic when function args list is empty](https://github.com/apache/dubbo-go/pull/98) +- [url.Values is not safe map](https://github.com/apache/dubbo-go/pull/172) diff --git a/README.md b/README.md index 2030664dba..1dde951d35 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,13 @@ Apache License, Version 2.0 ## Release note ## -[v1.0.0 - May 29, 2019 compatible with dubbo v2.6.5](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) +[v1.3.0 - Mar 1, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.3.0) + +[v1.2.0 - Nov 15, 2019](https://github.com/apache/dubbo-go/releases/tag/v1.2.0) [v1.1.0 - Sep 7, 2019 the first release after transferred to apache](https://github.com/apache/dubbo-go/releases/tag/v1.1.0) -[v1.2.0 - Nov 15, 2019](https://github.com/apache/dubbo-go/releases/tag/v1.2.0) +[v1.0.0 - May 29, 2019 compatible with dubbo v2.6.5](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) ## Project Architecture ## @@ -43,18 +45,18 @@ Finished List: - Codec * JsonRPC V2 * Hessian V2 - + - Protocol * Dubbo * Jsonrpc2.0 * [gRPC](https://github.com/apache/dubbo-go/pull/311) - + - Registry * ZooKeeper * [etcd v3](https://github.com/apache/dubbo-go/pull/148) * [nacos](https://github.com/apache/dubbo-go/pull/151) * [consul](https://github.com/apache/dubbo-go/pull/121) - + - Dynamic Configure Center & Service Management Configurator * Zookeeper * [apollo](https://github.com/apache/dubbo-go/pull/250) @@ -66,12 +68,13 @@ Finished List: * [Available](https://github.com/apache/dubbo-go/pull/155) * [Broadcast](https://github.com/apache/dubbo-go/pull/158) * [Forking](https://github.com/apache/dubbo-go/pull/161) - + - Load Balance * Random * [RoundRobin](https://github.com/apache/dubbo-go/pull/66) * [LeastActive](https://github.com/apache/dubbo-go/pull/65) - + * [ConsistentHash](https://github.com/apache/dubbo-go/pull/261) + - Filter * Echo Health Check * [Circuit break and service downgrade](https://github.com/apache/dubbo-go/pull/133) @@ -80,10 +83,10 @@ Finished List: * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237) * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246) * [GenericServiceFilter](https://github.com/apache/dubbo-go/pull/291) - + - Invoke * [generic invoke](https://github.com/apache/dubbo-go/pull/122) - + - Others: * start check * connecting certain provider @@ -94,14 +97,13 @@ Finished List: Working List: -- Load Balance: ConsistentHash - Registry: k8s - Metadata Center (dubbo v2.7.x) - Metrics: Opentracing/Promethus(dubbo v2.7.x) You can know more about dubbo-go by its [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap). -![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/arch.png) +![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/dubbo-go-arch.png) ## Document @@ -125,7 +127,7 @@ Windows before_ut.bat ``` -# Run +### Run ```bash go test ./... @@ -133,6 +135,10 @@ go test ./... go test ./... -coverprofile=coverage.txt -covermode=atomic ``` +## Build + +Please move to [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) + ## Contributing If you are willing to do some code contributions and document contributions to [Apache/dubbo-go](https://github.com/apache/dubbo-go), please visit [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md). @@ -147,11 +153,6 @@ About dubbo-go benchmarking report, please refer to [dubbo benchmarking report]( If you are using [apache/dubbo-go](github.com/apache/dubbo-go) and think that it helps you or want do some contributions to it, please add your company to to [the user list](https://github.com/apache/dubbo-go/issues/2) to let us know your needs. - -![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png)![Excellent Health Technology Group](https://raw.githubusercontent.com/dajiiu/photo/static/mirror/haozhuo_logo.png) -![ctrip](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png) - -## Stargazers - -[![Stargazers over time](https://starchart.cc/apache/dubbo-go.svg)](https://starchart.cc/apache/dubbo-go) - +![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png) +![Excellent Health Technology Group](https://raw.githubusercontent.com/dajiiu/photo/static/mirror/haozhuo_logo.png) +![tuya](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png) diff --git a/README_CN.md b/README_CN.md index 22af253416..ade924e7a9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,6 +2,7 @@ [![Build Status](https://travis-ci.org/apache/dubbo-go.svg?branch=master)](https://travis-ci.org/apache/dubbo-go) [![codecov](https://codecov.io/gh/apache/dubbo-go/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/dubbo-go) +[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/apache/dubbo-go?tab=doc) --- Apache Dubbo Go 语言实现 @@ -12,11 +13,13 @@ Apache License, Version 2.0 ## 发布日志 ## -[v1.0.0 - 2019年5月29日 兼容dubbo v2.6.5 版本](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) +[v1.3.0 - 2020年3月1日](https://github.com/apache/dubbo-go/releases/tag/v1.3.0) + +[v1.2.0 - 2019年11月15日](https://github.com/apache/dubbo-go/releases/tag/v1.2.0) [v1.1.0 - 2019年9月7日 捐献给Apache之后的第一次release](https://github.com/apache/dubbo-go/releases/tag/v1.1.0) -[v1.2.0 - 2019年11月15日](https://github.com/apache/dubbo-go/releases/tag/v1.2.0) +[v1.0.0 - 2019年5月29日 兼容dubbo v2.6.5 版本](https://github.com/apache/dubbo-go/releases/tag/v1.0.0) ## 工程架构 ## @@ -33,7 +36,7 @@ Apache License, Version 2.0 - 角色端 * Consumer * Provider - + - 传输协议 * HTTP * TCP @@ -46,17 +49,17 @@ Apache License, Version 2.0 * Dubbo * Jsonrpc2.0 * [gRPC](https://github.com/apache/dubbo-go/pull/311) - + - 注册中心 * ZooKeeper * [etcd v3](https://github.com/apache/dubbo-go/pull/148) * [nacos](https://github.com/apache/dubbo-go/pull/151) * [consul](https://github.com/apache/dubbo-go/pull/121) - + - 动态配置中心与服务治理配置器 * Zookeeper * [apollo](https://github.com/apache/dubbo-go/pull/250) - + - 集群策略 * Failover * [Failfast](https://github.com/apache/dubbo-go/pull/140) @@ -64,12 +67,13 @@ Apache License, Version 2.0 * [Available](https://github.com/apache/dubbo-go/pull/155) * [Broadcast](https://github.com/apache/dubbo-go/pull/158) * [Forking](https://github.com/apache/dubbo-go/pull/161) - + - 负载均衡策略 * Random * [RoundRobin](https://github.com/apache/dubbo-go/pull/66) * [LeastActive](https://github.com/apache/dubbo-go/pull/65) - + * [ConsistentHash](https://github.com/apache/dubbo-go/pull/261) + - 过滤器 * Echo Health Check * [服务熔断&降级](https://github.com/apache/dubbo-go/pull/133) @@ -77,10 +81,10 @@ Apache License, Version 2.0 * [AccessLogFilter](https://github.com/apache/dubbo-go/pull/214) * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237) * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246) - + - 调用 * [泛化调用](https://github.com/apache/dubbo-go/pull/122) - + - 其他功能支持: * 启动时检查 * 服务直连 @@ -91,14 +95,13 @@ Apache License, Version 2.0 开发中列表: -- 负载均衡策略: ConsistentHash - 注册中心: k8s - 元数据中心 (dubbo v2.7.x) - Metrics: Opentracing/Promethus(dubbo v2.7.x) 你可以通过访问 [roadmap](https://github.com/apache/dubbo-go/wiki/Roadmap) 知道更多关于 dubbo-go 的信息。 -![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/arch.png) +![feature](https://raw.githubusercontent.com/wiki/apache/dubbo-go/dubbo-go-arch.png) ## 文档 @@ -122,7 +125,7 @@ Windows before_ut.bat ``` -# 执行 +### 执行 ```bash go test ./... @@ -130,6 +133,10 @@ go test ./... go test ./... -coverprofile=coverage.txt -covermode=atomic ``` +## 编译 + +请移步 [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) + ## 如何贡献 如果您愿意给 [Apache/dubbo-go](https://github.com/apache/dubbo-go) 贡献代码或者文档,我们都热烈欢迎。具体请参考 [contribution intro](https://github.com/apache/dubbo-go/blob/master/contributing.md)。 @@ -145,8 +152,5 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic 若你正在使用 [apache/dubbo-go](github.com/apache/dubbo-go) 且认为其有用或者向对其做改进,请忝列贵司信息于 [用户列表](https://github.com/apache/dubbo-go/issues/2),以便我们知晓之。 ![ctrip](https://pic.c-ctrip.com/common/c_logo2013.png) - -## Stargazers - -[![Stargazers over time](https://starchart.cc/apache/dubbo-go.svg)](https://starchart.cc/apache/dubbo-go) - +![Excellent Health Technology Group](https://raw.githubusercontent.com/dajiiu/photo/static/mirror/haozhuo_logo.png) +![tuya](https://raw.githubusercontent.com/pantianying/go-tool/master/picture/logo_2-removebg-preview.png) diff --git a/before_ut.bat b/before_ut.bat index 5296d0f876..abe7fc250e 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 323173bcc6..7ee92e57a2 --- a/before_ut.sh +++ b/before_ut.sh @@ -14,8 +14,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +zkJarName="zookeeper-3.4.9-fatjar.jar" +remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/${zkJarName}" +zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" +zkJar="${zkJarPath}/${zkJarName}" -mkdir -p remoting/zookeeper/zookeeper-4unittest/contrib/fatjar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar registry/zookeeper/zookeeper-4unittest/contrib/fatjar -wget -P "remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar -cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ -cp remoting/zookeeper/zookeeper-4unittest/contrib/fatjar/zookeeper-3.4.9-fatjar.jar registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ \ No newline at end of file +if [ ! -f "${zkJar}" ]; then + mkdir -p ${zkJarPath} + wget -P "${zkJarPath}" ${remoteJarUrl} +fi + +mkdir -p config_center/zookeeper/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/ + +mkdir -p registry/zookeeper/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} registry/zookeeper/zookeeper-4unittest/contrib/fatjar/ + +mkdir -p cluster/router/chain/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/chain/zookeeper-4unittest/contrib/fatjar + +mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar \ No newline at end of file diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index 644f67c524..1279999412 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -45,6 +45,7 @@ func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker { destroyed: atomic.NewBool(false), } } + func (invoker *baseClusterInvoker) GetUrl() common.URL { return invoker.directory.GetUrl() } diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go index 49df78c41b..d074697b85 100644 --- a/cluster/cluster_impl/base_cluster_invoker_test.go +++ b/cluster/cluster_impl/base_cluster_invoker_test.go @@ -47,6 +47,7 @@ func Test_StickyNormal(t *testing.T) { result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) assert.Equal(t, result, result1) } + func Test_StickyNormalWhenError(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index 99c584bb58..1be21067a6 100644 --- a/cluster/cluster_impl/failover_cluster_test.go +++ b/cluster/cluster_impl/failover_cluster_test.go @@ -118,6 +118,7 @@ func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocatio } return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) } + func Test_FailoverInvokeSuccess(t *testing.T) { urlParams := url.Values{} result := normalInvoke(t, 3, urlParams) diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index e1a38c4c82..75d9ef2656 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -20,39 +20,94 @@ package directory import ( "sync" ) + import ( + "github.com/dubbogo/gost/container/set" "go.uber.org/atomic" ) + import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" ) -// BaseDirectory ... +var routerURLSet = gxset.NewSet() + +// BaseDirectory Abstract implementation of Directory: Invoker list returned from this Directory's list method have been filtered by Routers type BaseDirectory struct { url *common.URL destroyed *atomic.Bool - mutex sync.Mutex + // this mutex for change the properties in BaseDirectory, like routerChain , destroyed etc + mutex sync.Mutex + routerChain router.Chain } -// NewBaseDirectory ... +// NewBaseDirectory Create BaseDirectory with URL func NewBaseDirectory(url *common.URL) BaseDirectory { return BaseDirectory{ - url: url, - destroyed: atomic.NewBool(false), + url: url, + destroyed: atomic.NewBool(false), + routerChain: &chain.RouterChain{}, } } -// GetUrl ... +// RouterChain Return router chain in directory +func (dir *BaseDirectory) RouterChain() router.Chain { + return dir.routerChain +} + +// SetRouterChain Set router chain in directory +func (dir *BaseDirectory) SetRouterChain(routerChain router.Chain) { + dir.mutex.Lock() + defer dir.mutex.Unlock() + dir.routerChain = routerChain +} + +// GetUrl Get URL func (dir *BaseDirectory) GetUrl() common.URL { return *dir.url } -// GetDirectoryUrl ... +// GetDirectoryUrl Get URL instance func (dir *BaseDirectory) GetDirectoryUrl() *common.URL { return dir.url } -// Destroy ... +// SetRouters Convert url to routers and add them into dir.routerChain +func (dir *BaseDirectory) SetRouters(urls []*common.URL) { + if len(urls) == 0 { + return + } + + routers := make([]router.Router, 0, len(urls)) + + for _, url := range urls { + routerKey := url.GetParam(constant.ROUTER_KEY, "") + + if len(routerKey) > 0 { + factory := extension.GetRouterFactory(url.Protocol) + r, err := factory.NewRouter(url) + if err != nil { + logger.Errorf("Create router fail. router key: %s, error: %v", routerKey, url.Service(), err) + return + } + routers = append(routers, r) + } + } + + logger.Infof("Init file condition router success, size: %v", len(routers)) + dir.mutex.Lock() + rc := dir.routerChain + dir.mutex.Unlock() + + rc.AddRouters(routers) +} + +// Destroy Destroy func (dir *BaseDirectory) Destroy(doDestroy func()) { if dir.destroyed.CAS(false, true) { dir.mutex.Lock() @@ -61,7 +116,18 @@ func (dir *BaseDirectory) Destroy(doDestroy func()) { } } -// IsAvailable ... +// IsAvailable Once directory init finish, it will change to true func (dir *BaseDirectory) IsAvailable() bool { return !dir.destroyed.Load() } + +// GetRouterURLSet Return router URL +func GetRouterURLSet() *gxset.HashSet { + return routerURLSet +} + +// AddRouterURLSet Add router URL +// Router URL will init in config/config_loader.go +func AddRouterURLSet(url *common.URL) { + routerURLSet.Add(url) +} diff --git a/cluster/directory/base_directory_test.go b/cluster/directory/base_directory_test.go new file mode 100644 index 0000000000..d5993959f1 --- /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 7d2d5490b0..9f600fedc4 100644 --- a/cluster/directory/static_directory.go +++ b/cluster/directory/static_directory.go @@ -18,6 +18,11 @@ package directory import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/cluster/router/chain" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" ) @@ -27,7 +32,7 @@ type staticDirectory struct { invokers []protocol.Invoker } -// NewStaticDirectory ... +// NewStaticDirectory Create a new staticDirectory with invokers func NewStaticDirectory(invokers []protocol.Invoker) *staticDirectory { var url common.URL @@ -53,11 +58,21 @@ func (dir *staticDirectory) IsAvailable() bool { return true } +// List List invokers func (dir *staticDirectory) List(invocation protocol.Invocation) []protocol.Invoker { - //TODO:Here should add router - return dir.invokers + l := len(dir.invokers) + invokers := make([]protocol.Invoker, l, l) + copy(invokers, dir.invokers) + routerChain := dir.RouterChain() + + if routerChain == nil { + return invokers + } + dirUrl := dir.GetUrl() + return routerChain.Route(invokers, &dirUrl, invocation) } +// Destroy Destroy func (dir *staticDirectory) Destroy() { dir.BaseDirectory.Destroy(func() { for _, ivk := range dir.invokers { @@ -66,3 +81,17 @@ func (dir *staticDirectory) Destroy() { dir.invokers = []protocol.Invoker{} }) } + +// BuildRouterChain build router chain by invokers +func (dir *staticDirectory) BuildRouterChain(invokers []protocol.Invoker) error { + if len(invokers) == 0 { + return perrors.Errorf("invokers == null") + } + url := invokers[0].GetUrl() + routerChain, e := chain.NewRouterChain(&url) + if e != nil { + return e + } + dir.SetRouterChain(routerChain) + return nil +} diff --git a/cluster/directory/static_directory_test.go b/cluster/directory/static_directory_test.go index 42ef1bcd0b..c50c9a4063 100644 --- a/cluster/directory/static_directory_test.go +++ b/cluster/directory/static_directory_test.go @@ -40,7 +40,9 @@ func Test_StaticDirList(t *testing.T) { } staticDir := NewStaticDirectory(invokers) - assert.Len(t, staticDir.List(&invocation.RPCInvocation{}), 10) + list := staticDir.List(&invocation.RPCInvocation{}) + + assert.Len(t, list, 10) } func Test_StaticDirDestroy(t *testing.T) { diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go new file mode 100644 index 0000000000..d48a837eba --- /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 0000000000..0cb47c4a18 --- /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 0000000000..056e32851c --- /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 0000000000..bd817af36c --- /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 0000000000..66512a1387 --- /dev/null +++ b/cluster/router/condition/factory.go @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" +) + +func init() { + extension.SetRouterFactory(constant.ConditionRouterName, newConditionRouterFactory) + extension.SetRouterFactory(constant.ConditionAppRouterName, newAppRouterFactory) +} + +// ConditionRouterFactory Condition router factory +type ConditionRouterFactory struct{} + +func newConditionRouterFactory() router.RouterFactory { + return &ConditionRouterFactory{} +} + +// NewRouter Create ConditionRouterFactory by URL +func (c *ConditionRouterFactory) NewRouter(url *common.URL) (router.Router, error) { + return NewConditionRouter(url) +} + +// NewRouter Create FileRouterFactory by Content +func (c *ConditionRouterFactory) NewFileRouter(content []byte) (router.Router, error) { + return NewFileConditionRouter(content) +} + +// AppRouterFactory Application router factory +type AppRouterFactory struct{} + +func newAppRouterFactory() router.RouterFactory { + return &AppRouterFactory{} +} + +// NewRouter Create AppRouterFactory by URL +func (c *AppRouterFactory) NewRouter(url *common.URL) (router.Router, error) { + return NewAppRouter(url) +} diff --git a/cluster/router/condition_router_test.go b/cluster/router/condition/factory_test.go similarity index 74% rename from cluster/router/condition_router_test.go rename to cluster/router/condition/factory_test.go index fc639b9fc7..99cec34096 100644 --- a/cluster/router/condition_router_test.go +++ b/cluster/router/condition/factory_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package router +package condition import ( "context" @@ -119,33 +119,33 @@ func (bi *MockInvoker) Destroy() { func TestRoute_matchWhen(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4")) - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) cUrl, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService") - matchWhen, _ := router.(*ConditionRouter).MatchWhen(cUrl, inv) + matchWhen := router.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen) rule1 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router1, _ := NewConditionRouterFactory().Router(getRouteUrl(rule1)) - matchWhen1, _ := router1.(*ConditionRouter).MatchWhen(cUrl, inv) + router1, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule1)) + matchWhen1 := router1.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen1) rule2 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 & host !=1.1.1.1 => host = 1.2.3.4")) - router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2)) - matchWhen2, _ := router2.(*ConditionRouter).MatchWhen(cUrl, inv) + router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2)) + matchWhen2 := router2.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, false, matchWhen2) rule3 := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.4 & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3)) - matchWhen3, _ := router3.(*ConditionRouter).MatchWhen(cUrl, inv) + router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3)) + matchWhen3 := router3.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen3) rule4 := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router4, _ := NewConditionRouterFactory().Router(getRouteUrl(rule4)) - matchWhen4, _ := router4.(*ConditionRouter).MatchWhen(cUrl, inv) + router4, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule4)) + matchWhen4 := router4.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen4) rule5 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.1 => host = 1.2.3.4")) - router5, _ := NewConditionRouterFactory().Router(getRouteUrl(rule5)) - matchWhen5, _ := router5.(*ConditionRouter).MatchWhen(cUrl, inv) + router5, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule5)) + matchWhen5 := router5.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, false, matchWhen5) rule6 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4")) - router6, _ := NewConditionRouterFactory().Router(getRouteUrl(rule6)) - matchWhen6, _ := router6.(*ConditionRouter).MatchWhen(cUrl, inv) + router6, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule6)) + matchWhen6 := router6.(*ConditionRouter).MatchWhen(&cUrl, inv) assert.Equal(t, true, matchWhen6) } @@ -162,19 +162,19 @@ func TestRoute_matchFilter(t *testing.T) { rule4 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.2,10.20.3.3,10.20.3.4")) rule5 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host != 10.20.3.3")) rule6 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " serialization = fastjson")) - router1, _ := NewConditionRouterFactory().Router(getRouteUrl(rule1)) - router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2)) - router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3)) - router4, _ := NewConditionRouterFactory().Router(getRouteUrl(rule4)) - router5, _ := NewConditionRouterFactory().Router(getRouteUrl(rule5)) - router6, _ := NewConditionRouterFactory().Router(getRouteUrl(rule6)) + router1, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule1)) + router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2)) + router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3)) + router4, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule4)) + router5, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule5)) + router6, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule6)) cUrl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - fileredInvokers1 := router1.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers2 := router2.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers3 := router3.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers4 := router4.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers5 := router5.Route(invokers, cUrl, &invocation.RPCInvocation{}) - fileredInvokers6 := router6.Route(invokers, cUrl, &invocation.RPCInvocation{}) + fileredInvokers1 := router1.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers2 := router2.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers3 := router3.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers4 := router4.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers5 := router5.Route(invokers, &cUrl, &invocation.RPCInvocation{}) + fileredInvokers6 := router6.Route(invokers, &cUrl, &invocation.RPCInvocation{}) assert.Equal(t, 1, len(fileredInvokers1)) assert.Equal(t, 0, len(fileredInvokers2)) assert.Equal(t, 0, len(fileredInvokers3)) @@ -187,22 +187,22 @@ func TestRoute_matchFilter(t *testing.T) { func TestRoute_methodRoute(t *testing.T) { inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("getFoo"), invocation.WithParameterTypes([]reflect.Type{}), invocation.WithArguments([]interface{}{})) rule := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) url, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo") - matchWhen, _ := router.(*ConditionRouter).MatchWhen(url, inv) + matchWhen := router.(*ConditionRouter).MatchWhen(&url, inv) assert.Equal(t, true, matchWhen) url1, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") - matchWhen, _ = router.(*ConditionRouter).MatchWhen(url1, inv) + matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv) assert.Equal(t, true, matchWhen) url2, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") rule2 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host!=1.1.1.1 => host = 1.2.3.4")) - router2, _ := NewConditionRouterFactory().Router(getRouteUrl(rule2)) - matchWhen, _ = router2.(*ConditionRouter).MatchWhen(url2, inv) + router2, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule2)) + matchWhen = router2.(*ConditionRouter).MatchWhen(&url2, inv) assert.Equal(t, false, matchWhen) url3, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") rule3 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host=1.1.1.1 => host = 1.2.3.4")) - router3, _ := NewConditionRouterFactory().Router(getRouteUrl(rule3)) - matchWhen, _ = router3.(*ConditionRouter).MatchWhen(url3, inv) + router3, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule3)) + matchWhen = router3.(*ConditionRouter).MatchWhen(&url3, inv) assert.Equal(t, true, matchWhen) } @@ -214,8 +214,8 @@ func TestRoute_ReturnFalse(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } @@ -226,19 +226,24 @@ func TestRoute_ReturnEmpty(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => ")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } func TestRoute_ReturnAll(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - invokers := []protocol.Invoker{&MockInvoker{}, &MockInvoker{}, &MockInvoker{}} + urlString := "dubbo://" + localIP + "/com.foo.BarService" + dubboURL, _ := common.NewURL(urlString) + mockInvoker1 := NewMockInvoker(dubboURL, 1) + mockInvoker2 := NewMockInvoker(dubboURL, 1) + mockInvoker3 := NewMockInvoker(dubboURL, 1) + invokers := []protocol.Invoker{mockInvoker1, mockInvoker2, mockInvoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } @@ -254,8 +259,8 @@ func TestRoute_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -273,8 +278,8 @@ func TestRoute_Empty_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -292,8 +297,8 @@ func TestRoute_False_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -311,8 +316,8 @@ func TestRoute_Placeholder(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) assert.Equal(t, invoker2, fileredInvokers[0]) assert.Equal(t, invoker3, fileredInvokers[1]) @@ -330,8 +335,8 @@ func TestRoute_NoForce(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrlWithNoForce(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithNoForce(rule)) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } @@ -347,7 +352,17 @@ func TestRoute_Force(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") - router, _ := NewConditionRouterFactory().Router(getRouteUrlWithForce(rule, "true")) - fileredInvokers := router.(*ConditionRouter).Route(invokers, curl, inv) + router, _ := newConditionRouterFactory().NewRouter(getRouteUrlWithForce(rule, "true")) + fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } + +func TestNewConditionRouterFactory(t *testing.T) { + factory := newConditionRouterFactory() + assert.NotNil(t, factory) +} + +func TestNewAppRouterFactory(t *testing.T) { + factory := newAppRouterFactory() + assert.NotNil(t, factory) +} diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go new file mode 100644 index 0000000000..efeec53efc --- /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 0000000000..3092b12ff8 --- /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 0000000000..ba2fbb0eb2 --- /dev/null +++ b/cluster/router/condition/listenable_router.go @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "fmt" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/config" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/remoting" +) + +const ( + // Default priority for listenable router, use the maximum int64 value + listenableRouterDefaultPriority = ^int64(0) +) + +// listenableRouter Abstract router which listens to dynamic configuration +type listenableRouter struct { + conditionRouters []*ConditionRouter + routerRule *RouterRule + url *common.URL + force bool + priority int64 +} + +// RouterRule Get RouterRule instance from listenableRouter +func (l *listenableRouter) RouterRule() *RouterRule { + return l.routerRule +} + +func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { + if ruleKey == "" { + return nil, perrors.Errorf("NewListenableRouter ruleKey is nil, can't create Listenable router") + } + l := &AppRouter{} + + l.url = url + l.priority = listenableRouterDefaultPriority + + routerKey := ruleKey + constant.ConditionRouterRuleSuffix + //add listener + dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() + if dynamicConfiguration == nil { + return nil, perrors.Errorf("Get dynamicConfiguration fail, dynamicConfiguration is nil, init config center plugin please") + } + + dynamicConfiguration.AddListener(routerKey, l) + //get rule + rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP)) + if len(rule) == 0 || err != nil { + return nil, perrors.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) + } + l.Process(&config_center.ConfigChangeEvent{ + Key: routerKey, + Value: rule, + ConfigType: remoting.EventTypeUpdate}) + + logger.Info("Init app router success") + return l, nil +} + +// Process Process config change event , generate routers and set them to the listenableRouter instance +func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) { + logger.Infof("Notification of condition rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) + if remoting.EventTypeDel == event.ConfigType { + l.routerRule = nil + if l.conditionRouters != nil { + l.conditionRouters = l.conditionRouters[:0] + } + return + } + content, ok := event.Value.(string) + if !ok { + msg := fmt.Sprintf("Convert event content fail,raw content:[%s] ", event.Value) + logger.Error(msg) + return + } + + routerRule, err := Parse(content) + if err != nil { + logger.Errorf("Parse condition router rule fail,error:[%s] ", err) + return + } + l.generateConditions(routerRule) +} + +func (l *listenableRouter) generateConditions(rule *RouterRule) { + if rule == nil || !rule.Valid { + return + } + l.conditionRouters = make([]*ConditionRouter, 0, len(rule.Conditions)) + l.routerRule = rule + for _, c := range rule.Conditions { + router, e := NewConditionRouterWithRule(c) + if e != nil { + logger.Errorf("Create condition router with rule fail,raw rule:[%s] ", c) + continue + } + router.Force = rule.Force + router.enabled = rule.Enabled + l.conditionRouters = append(l.conditionRouters, router) + } +} + +// Route Determine the target invokers list. +func (l *listenableRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if len(invokers) == 0 || len(l.conditionRouters) == 0 { + return invokers + } + //We will check enabled status inside each router. + for _, r := range l.conditionRouters { + invokers = r.Route(invokers, url, invocation) + } + return invokers +} + +// Priority Return Priority in listenable router +func (l *listenableRouter) Priority() int64 { + return l.priority +} + +// URL Return URL in listenable router +func (l *listenableRouter) URL() common.URL { + return *l.url +} diff --git a/cluster/router/condition_router.go b/cluster/router/condition/router.go similarity index 63% rename from cluster/router/condition_router.go rename to cluster/router/condition/router.go index c38e9718cc..c5d46444bd 100644 --- a/cluster/router/condition_router.go +++ b/cluster/router/condition/router.go @@ -15,57 +15,55 @@ * limitations under the License. */ -package router +package condition import ( - "reflect" "regexp" "strings" ) import ( - gxset "github.com/dubbogo/gost/container/set" - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) import ( + matcher "github.com/apache/dubbo-go/cluster/router/match" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" + "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/net" ) const ( - //ROUTE_PATTERN route pattern regex - ROUTE_PATTERN = `([&!=,]*)\\s*([^&!=,\\s]+)` - // FORCE ... - FORCE = "force" - // PRIORITY ... - PRIORITY = "priority" + //pattern route pattern regex + pattern = `([&!=,]*)\\s*([^&!=,\\s]+)` ) -//ConditionRouter condition router struct +var ( + routerPatternReg = regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`) +) + +// ConditionRouter Condition router struct type ConditionRouter struct { Pattern string - Url *common.URL - Priority int64 + url *common.URL + priority int64 Force bool + enabled bool WhenCondition map[string]MatchPair ThenCondition map[string]MatchPair } -func newConditionRouter(url *common.URL) (*ConditionRouter, error) { +// NewConditionRouterWithRule Init condition router by raw rule +func NewConditionRouterWithRule(rule string) (*ConditionRouter, error) { var ( whenRule string thenRule string when map[string]MatchPair then map[string]MatchPair ) - rule, err := url.GetParamAndDecoded(constant.RULE_KEY) - if err != nil || len(rule) == 0 { - return nil, perrors.Errorf("Illegal route rule!") - } rule = strings.Replace(rule, "consumer.", "", -1) rule = strings.Replace(rule, "provider.", "", -1) i := strings.Index(rule, "=>") @@ -98,31 +96,61 @@ func newConditionRouter(url *common.URL) (*ConditionRouter, error) { then = t } return &ConditionRouter{ - ROUTE_PATTERN, - url, - url.GetParamInt(PRIORITY, 0), - url.GetParamBool(FORCE, false), - when, - then, + Pattern: pattern, + WhenCondition: when, + ThenCondition: then, }, nil } -// Route -// Router determine the target server list. -func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, invocation protocol.Invocation) []protocol.Invoker { - if len(invokers) == 0 { - return invokers +// NewConditionRouter Init condition router by URL +func NewConditionRouter(url *common.URL) (*ConditionRouter, error) { + if url == nil { + return nil, perrors.Errorf("Illegal route URL!") + } + rule, err := url.GetParamAndDecoded(constant.RULE_KEY) + if err != nil || len(rule) == 0 { + return nil, perrors.Errorf("Illegal route rule!") } - isMatchWhen, err := c.MatchWhen(url, invocation) + + router, err := NewConditionRouterWithRule(rule) if err != nil { + return nil, err + } - var urls []string - for _, invo := range invokers { - urls = append(urls, reflect.TypeOf(invo).String()) - } - logger.Warnf("Failed to execute condition router rule: %s , invokers: [%s], cause: %v", c.Url.String(), strings.Join(urls, ","), err) + router.url = url + router.priority = url.GetParamInt(constant.RouterPriority, 0) + router.Force = url.GetParamBool(constant.RouterForce, false) + router.enabled = url.GetParamBool(constant.RouterEnabled, true) + + return router, nil +} + +// Priority Return Priority in condition router +func (c *ConditionRouter) Priority() int64 { + return c.priority +} + +// URL Return URL in condition router +func (c *ConditionRouter) URL() common.URL { + return *c.url +} + +// Enabled Return is condition router is enabled +// true: enabled +// false: disabled +func (c *ConditionRouter) Enabled() bool { + return c.enabled +} + +// Route Determine the target invokers list. +func (c *ConditionRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if !c.Enabled() { + return invokers + } + if len(invokers) == 0 { return invokers } + isMatchWhen := c.MatchWhen(url, invocation) if !isMatchWhen { return invokers } @@ -130,17 +158,9 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, inv if len(c.ThenCondition) == 0 { return result } - localIP, _ := gxnet.GetLocalIP() for _, invoker := range invokers { - isMatchThen, err := c.MatchThen(invoker.GetUrl(), url) - if err != nil { - var urls []string - for _, invo := range invokers { - urls = append(urls, reflect.TypeOf(invo).String()) - } - logger.Warnf("Failed to execute condition router rule: %s , invokers: [%s], cause: %v", c.Url.String(), strings.Join(urls, ","), err) - return invokers - } + invokerUrl := invoker.GetUrl() + isMatchThen := c.MatchThen(&invokerUrl, url) if isMatchThen { result = append(result, invoker) } @@ -149,6 +169,7 @@ func (c *ConditionRouter) Route(invokers []protocol.Invoker, url common.URL, inv return result } else if c.Force { rule, _ := url.GetParamAndDecoded(constant.RULE_KEY) + localIP, _ := gxnet.GetLocalIP() logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule) return result } @@ -162,15 +183,10 @@ func parseRule(rule string) (map[string]MatchPair, error) { } var ( - pair MatchPair - startIndex int + pair MatchPair ) values := gxset.NewSet() - reg := regexp.MustCompile(`([&!=,]*)\s*([^&!=,\s]+)`) - if indexTuple := reg.FindIndex([]byte(rule)); len(indexTuple) > 0 { - startIndex = indexTuple[0] - } - matches := reg.FindAllSubmatch([]byte(rule), -1) + matches := routerPatternReg.FindAllSubmatch([]byte(rule), -1) for _, groups := range matches { separator := string(groups[1]) content := string(groups[2]) @@ -193,22 +209,26 @@ func parseRule(rule string) (map[string]MatchPair, error) { } case "=": if &pair == nil { + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } values = pair.Matches values.Add(content) case "!=": if &pair == nil { + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } values = pair.Mismatches values.Add(content) case ",": if values.Empty() { + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } values.Add(content) default: + var startIndex = getStartIndex(rule) return nil, perrors.Errorf("Illegal route rule \"%s\", The error char '%s' at index %d before \"%d\".", rule, separator, startIndex, startIndex) } @@ -216,23 +236,31 @@ func parseRule(rule string) (map[string]MatchPair, error) { return condition, nil } -//MatchWhen MatchWhen -func (c *ConditionRouter) MatchWhen(url common.URL, invocation protocol.Invocation) (bool, error) { - condition, err := MatchCondition(c.WhenCondition, &url, nil, invocation) - return len(c.WhenCondition) == 0 || condition, err +func getStartIndex(rule string) int { + if indexTuple := routerPatternReg.FindIndex([]byte(rule)); len(indexTuple) > 0 { + return indexTuple[0] + } + return -1 } -//MatchThen MatchThen -func (c *ConditionRouter) MatchThen(url common.URL, param common.URL) (bool, error) { - condition, err := MatchCondition(c.ThenCondition, &url, ¶m, nil) - return len(c.ThenCondition) > 0 && condition, err +// MatchWhen MatchWhen +func (c *ConditionRouter) MatchWhen(url *common.URL, invocation protocol.Invocation) bool { + condition := matchCondition(c.WhenCondition, url, nil, invocation) + return len(c.WhenCondition) == 0 || condition } -//MatchCondition MatchCondition -func MatchCondition(pairs map[string]MatchPair, url *common.URL, param *common.URL, invocation protocol.Invocation) (bool, error) { +// MatchThen MatchThen +func (c *ConditionRouter) MatchThen(url *common.URL, param *common.URL) bool { + condition := matchCondition(c.ThenCondition, url, param, nil) + return len(c.ThenCondition) > 0 && condition +} + +// MatchCondition MatchCondition +func matchCondition(pairs map[string]MatchPair, url *common.URL, param *common.URL, invocation protocol.Invocation) bool { sample := url.ToMap() if sample == nil { - return true, perrors.Errorf("url is not allowed be nil") + // because url.ToMap() may return nil, but it should continue to process make condition + sample = make(map[string]string) } var result bool for key, matchPair := range pairs { @@ -248,22 +276,22 @@ func MatchCondition(pairs map[string]MatchPair, url *common.URL, param *common.U } if len(sampleValue) > 0 { if !matchPair.isMatch(sampleValue, param) { - return false, nil + return false } result = true } else { if !(matchPair.Matches.Empty()) { - return false, nil + return false } result = true } } - return result, nil + return result } -// MatchPair ... +// MatchPair Match key pair , condition process type MatchPair struct { Matches *gxset.HashSet Mismatches *gxset.HashSet @@ -273,7 +301,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { if !pair.Matches.Empty() && pair.Mismatches.Empty() { for match := range pair.Matches.Items { - if isMatchGlobPattern(match.(string), value, param) { + if matcher.IsMatchGlobalPattern(match.(string), value, param) { return true } } @@ -282,20 +310,21 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { if !pair.Mismatches.Empty() && pair.Matches.Empty() { for mismatch := range pair.Mismatches.Items { - if isMatchGlobPattern(mismatch.(string), value, param) { + if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) { return false } } return true } if !pair.Mismatches.Empty() && !pair.Matches.Empty() { + //when both mismatches and matches contain the same value, then using mismatches first for mismatch := range pair.Mismatches.Items { - if isMatchGlobPattern(mismatch.(string), value, param) { + if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) { return false } } for match := range pair.Matches.Items { - if isMatchGlobPattern(match.(string), value, param) { + if matcher.IsMatchGlobalPattern(match.(string), value, param) { return true } } @@ -303,31 +332,3 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { } return false } - -func isMatchGlobPattern(pattern string, value string, param *common.URL) bool { - if param != nil && strings.HasPrefix(pattern, "$") { - pattern = param.GetRawParam(pattern[1:]) - } - if "*" == pattern { - return true - } - if len(pattern) == 0 && len(value) == 0 { - return true - } - if len(pattern) == 0 || len(value) == 0 { - return false - } - i := strings.LastIndex(pattern, "*") - switch i { - case -1: - return value == pattern - case len(pattern) - 1: - return strings.HasPrefix(value, pattern[0:i]) - case 0: - return strings.HasSuffix(value, pattern[:i+1]) - default: - prefix := pattern[0:1] - suffix := pattern[i+1:] - return strings.HasPrefix(value, prefix) && strings.HasSuffix(value, suffix) - } -} diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go new file mode 100644 index 0000000000..1374cf9de2 --- /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 0000000000..5acc728391 --- /dev/null +++ b/cluster/router/condition/router_rule_test.go @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package condition + +import ( + "testing" +) +import ( + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + testyml := ` +scope: application +runtime: true +force: false +conditions: + - > + method!=sayHello => + - > + ip=127.0.0.1 + => + 1.1.1.1` + rule, e := Parse(testyml) + + assert.Nil(t, e) + assert.NotNil(t, rule) + assert.Equal(t, 2, len(rule.Conditions)) + assert.Equal(t, "application", rule.Scope) + assert.True(t, rule.Runtime) + assert.Equal(t, false, rule.Force) + assert.Equal(t, testyml, rule.RawRule) + assert.True(t, true, rule.Valid) + assert.Equal(t, false, rule.Enabled) + assert.Equal(t, false, rule.Dynamic) + assert.Equal(t, "", rule.Key) +} diff --git a/cluster/router.go b/cluster/router/health_checker.go similarity index 67% rename from cluster/router.go rename to cluster/router/health_checker.go index 589eb9a269..d9e3087a27 100644 --- a/cluster/router.go +++ b/cluster/router/health_checker.go @@ -15,31 +15,14 @@ * limitations under the License. */ -package cluster +package router import ( - "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" ) -// Extension - Router - -// RouterFactory ... -type RouterFactory interface { - Router(*common.URL) (Router, error) -} - -// Router ... -type Router interface { - Route([]protocol.Invoker, common.URL, protocol.Invocation) []protocol.Invoker -} - -// RouterChain ... -type RouterChain struct { - routers []Router -} - -// NewRouterChain ... -func NewRouterChain(url common.URL) { - +// HealthChecker is used to determine whether the invoker is healthy or not +type HealthChecker interface { + // IsHealthy evaluates the healthy state on the given Invoker + IsHealthy(invoker protocol.Invoker) bool } diff --git a/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go new file mode 100644 index 0000000000..a26f86ddac --- /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 0000000000..74aa394074 --- /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 0000000000..32d84d145c --- /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 0000000000..a9d94da7c3 --- /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 0000000000..1ddc9ccb17 --- /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 0000000000..759ef93dbe --- /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 0000000000..28fe7151c5 --- /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 0000000000..f16480f1d3 --- /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 0000000000..a28002a09e --- /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 0000000000..42c08a7009 --- /dev/null +++ b/cluster/router/rule.go @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package router + +// BaseRouterRule +type BaseRouterRule struct { + RawRule string + Runtime bool + Force bool + Valid bool + Enabled bool + Priority int + Dynamic bool + Scope string + Key string +} diff --git a/common/constant/env.go b/common/constant/env.go index cb5394bb82..5376323328 100644 --- a/common/constant/env.go +++ b/common/constant/env.go @@ -24,4 +24,6 @@ const ( CONF_PROVIDER_FILE_PATH = "CONF_PROVIDER_FILE_PATH" // APP_LOG_CONF_FILE ... APP_LOG_CONF_FILE = "APP_LOG_CONF_FILE" + // CONF_ROUTER_FILE_PATH Specify Path variable of router config file + CONF_ROUTER_FILE_PATH = "CONF_ROUTER_FILE_PATH" ) diff --git a/common/constant/key.go b/common/constant/key.go index 33ce0a38b0..07335bed59 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -40,6 +40,7 @@ const ( TOKEN_KEY = "token" LOCAL_ADDR = "local-addr" REMOTE_ADDR = "remote-addr" + PATH_SEPARATOR = "/" ) const ( @@ -89,16 +90,23 @@ const ( ) const ( - APPLICATION_KEY = "application" - ORGANIZATION_KEY = "organization" - NAME_KEY = "name" - MODULE_KEY = "module" - APP_VERSION_KEY = "app.version" - OWNER_KEY = "owner" - ENVIRONMENT_KEY = "environment" - METHOD_KEY = "method" - METHOD_KEYS = "methods" - RULE_KEY = "rule" + APPLICATION_KEY = "application" + ORGANIZATION_KEY = "organization" + NAME_KEY = "name" + MODULE_KEY = "module" + APP_VERSION_KEY = "app.version" + OWNER_KEY = "owner" + ENVIRONMENT_KEY = "environment" + METHOD_KEY = "method" + METHOD_KEYS = "methods" + RULE_KEY = "rule" + RUNTIME_KEY = "runtime" + BACKUP_KEY = "backup" + ROUTERS_CATEGORY = "routers" + ROUTE_PROTOCOL = "route" + CONDITION_ROUTE_PROTOCOL = "condition" + PROVIDERS_CATEGORY = "providers" + ROUTER_KEY = "router" ) const ( @@ -120,6 +128,7 @@ const ( ProviderConfigPrefix = "dubbo.provider." ConsumerConfigPrefix = "dubbo.consumer." ShutdownConfigPrefix = "dubbo.shutdown." + RouterConfigPrefix = "dubbo.router." ) const ( @@ -142,6 +151,28 @@ const ( TRACING_REMOTE_SPAN_CTX = "tracing.remote.span.ctx" ) +// Use for router module +const ( + // ConditionRouterName Specify file condition router name + ConditionRouterName = "condition" + // ConditionAppRouterName Specify listenable application router name + ConditionAppRouterName = "app" + // ListenableRouterName Specify listenable router name + ListenableRouterName = "listenable" + // HealthCheckRouterName Specify the name of HealthCheckRouter + HealthCheckRouterName = "health_check" + + // ConditionRouterRuleSuffix Specify condition router suffix + ConditionRouterRuleSuffix = ".condition-router" + + // Force Force key in router module + RouterForce = "force" + // Enabled Enabled key in router module + RouterEnabled = "enabled" + // Priority Priority key in router module + RouterPriority = "priority" +) + const ( // name of consumer sign filter CONSUMER_SIGN_FILTER = "sign" @@ -174,3 +205,25 @@ const ( // key of secret access key SECRET_ACCESS_KEY_KEY = "secretAccessKey" ) + +// HealthCheck Router +const ( + // The key of HealthCheck SPI + HEALTH_CHECKER = "health.checker" + // The name of the default implementation of HealthChecker + DEFAULT_HEALTH_CHECKER = "default" + // The key of oustanding-request-limit + OUTSTANDING_REQUEST_COUNT_LIMIT_KEY = "outstanding.request.limit" + // The key of successive-failed-request's threshold + SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY = "successive.failed.threshold" + // The key of circuit-tripped timeout factor + CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY = "circuit.tripped.timeout.factor" + // The default threshold of successive-failed-request if not specfied + DEFAULT_SUCCESSIVE_FAILED_THRESHOLD = 5 + // The default maximum diff between successive-failed-request's threshold and actual successive-failed-request's count + DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF = 5 + // The default factor of circuit-tripped timeout if not specfied + DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR = 1000 + // The default time window of circuit-tripped in millisecond if not specfied + MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS = 30000 +) diff --git a/common/extension/health_checker.go b/common/extension/health_checker.go new file mode 100644 index 0000000000..365c5d0910 --- /dev/null +++ b/common/extension/health_checker.go @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" +) + +var ( + healthCheckers = make(map[string]func(url *common.URL) router.HealthChecker) +) + +// SethealthChecker set the HealthChecker with name +func SethealthChecker(name string, fcn func(url *common.URL) router.HealthChecker) { + healthCheckers[name] = fcn +} + +// GetHealthChecker get the HealthChecker with name +func GetHealthChecker(name string, url *common.URL) router.HealthChecker { + if healthCheckers[name] == nil { + panic("healthCheckers for " + name + " is not existing, make sure you have import the package.") + } + return healthCheckers[name](url) +} diff --git a/cluster/router/router_factory.go b/common/extension/health_checker_test.go similarity index 60% rename from cluster/router/router_factory.go rename to common/extension/health_checker_test.go index 723050939e..ec934e6e9c 100644 --- a/cluster/router/router_factory.go +++ b/common/extension/health_checker_test.go @@ -15,27 +15,35 @@ * limitations under the License. */ -package router +package extension import ( - "github.com/apache/dubbo-go/cluster" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" ) -func init() { - extension.SetRouterFactory("condition", NewConditionRouterFactory) +func TestGetHealthChecker(t *testing.T) { + SethealthChecker("mock", newMockhealthCheck) + checker := GetHealthChecker("mock", common.NewURLWithOptions()) + assert.NotNil(t, checker) } -// ConditionRouterFactory ... -type ConditionRouterFactory struct{} +type mockHealthChecker struct { +} -// NewConditionRouterFactory ... -func NewConditionRouterFactory() cluster.RouterFactory { - return ConditionRouterFactory{} +func (m mockHealthChecker) IsHealthy(invoker protocol.Invoker) bool { + return true } -// Router ... -func (c ConditionRouterFactory) Router(url *common.URL) (cluster.Router, error) { - return newConditionRouter(url) +func newMockhealthCheck(url *common.URL) router.HealthChecker { + return &mockHealthChecker{} } diff --git a/common/extension/rest_client.go b/common/extension/rest_client.go index 7096d23810..e63c9a36db 100644 --- a/common/extension/rest_client.go +++ b/common/extension/rest_client.go @@ -22,7 +22,7 @@ import ( ) var ( - restClients = make(map[string]func(restOptions *rest_interface.RestOptions) rest_interface.RestClient) + restClients = make(map[string]func(restOptions *rest_interface.RestOptions) rest_interface.RestClient, 8) ) func SetRestClient(name string, fun func(restOptions *rest_interface.RestOptions) rest_interface.RestClient) { diff --git a/common/extension/rest_server.go b/common/extension/rest_server.go index 06e1757e76..8ba5b65ca5 100644 --- a/common/extension/rest_server.go +++ b/common/extension/rest_server.go @@ -22,7 +22,7 @@ import ( ) var ( - restServers = make(map[string]func() rest_interface.RestServer) + restServers = make(map[string]func() rest_interface.RestServer, 8) ) func SetRestServer(name string, fun func() rest_interface.RestServer) { diff --git a/common/extension/router_factory.go b/common/extension/router_factory.go index c77cc29136..70d71dfa85 100644 --- a/common/extension/router_factory.go +++ b/common/extension/router_factory.go @@ -18,23 +18,50 @@ package extension import ( - "github.com/apache/dubbo-go/cluster" + "sync" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" ) var ( - routers = make(map[string]func() cluster.RouterFactory) + routers = make(map[string]func() router.RouterFactory) + fileRouterFactoryOnce sync.Once + fileRouterFactories = make(map[string]router.FIleRouterFactory) ) -// SetRouterFactory ... -func SetRouterFactory(name string, fun func() cluster.RouterFactory) { +// SetRouterFactory Set create router factory function by name +func SetRouterFactory(name string, fun func() router.RouterFactory) { routers[name] = fun } -// GetRouterFactory ... -func GetRouterFactory(name string) cluster.RouterFactory { +// GetRouterFactory Get create router factory function by name +func GetRouterFactory(name string) router.RouterFactory { if routers[name] == nil { panic("router_factory for " + name + " is not existing, make sure you have import the package.") } return routers[name]() +} +// GetRouterFactories Get all create router factory function +func GetRouterFactories() map[string]func() router.RouterFactory { + return routers +} + +// GetFileRouterFactories Get all create file router factory instance +func GetFileRouterFactories() map[string]router.FIleRouterFactory { + l := len(routers) + if l == 0 { + return nil + } + fileRouterFactoryOnce.Do(func() { + for k := range routers { + factory := GetRouterFactory(k) + if fr, ok := factory.(router.FIleRouterFactory); ok { + fileRouterFactories[k] = fr + } + } + }) + return fileRouterFactories } diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index 43ca720d0e..6765a810a5 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -59,6 +59,7 @@ func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[str // type XxxProvider struct { // Yyy func(ctx context.Context, args []interface{}, rsp *Zzz) error // } + func (p *Proxy) Implement(v common.RPCService) { // check parameters, incoming interface must be a elem's pointer. @@ -142,7 +143,7 @@ func (p *Proxy) Implement(v common.RPCService) { result := p.invoke.Invoke(invCtx, inv) err = result.Error() - logger.Infof("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) + logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) if len(outs) == 1 { return []reflect.Value{reflect.ValueOf(&err).Elem()} } diff --git a/common/url.go b/common/url.go index 360f0aa4ca..ebb648db27 100644 --- a/common/url.go +++ b/common/url.go @@ -59,8 +59,8 @@ const ( var ( // DubboNodes ... DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"} - // DubboRole ... - DubboRole = [...]string{"consumer", "", "", "provider"} + // DubboRole Dubbo service role + DubboRole = [...]string{"consumer", "", "routers", "provider"} ) // RoleType ... @@ -240,7 +240,7 @@ func NewURL(urlString string, opts ...option) (URL, error) { if strings.Contains(s.Location, ":") { s.Ip, s.Port, err = net.SplitHostPort(s.Location) if err != nil { - return s, perrors.Errorf("net.SplitHostPort(Url.Host{%s}), error{%v}", s.Location, err) + return s, perrors.Errorf("net.SplitHostPort(url.Host{%s}), error{%v}", s.Location, err) } } for _, opt := range opts { @@ -277,6 +277,7 @@ func (c URL) URLEqual(url URL) bool { } return true } + func isMatchCategory(category1 string, category2 string) bool { if len(category2) == 0 { return category1 == constant.DEFAULT_CATEGORY @@ -288,6 +289,7 @@ func isMatchCategory(category1 string, category2 string) bool { return strings.Contains(category2, category1) } } + func (c URL) String() string { var buildString string if len(c.Username) == 0 && len(c.Password) == 0 { @@ -373,7 +375,7 @@ func (c URL) Service() string { return service } else if c.SubURL != nil { service = c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) - if service != "" { //if url.path is "" then return suburl's path, special for registry Url + if service != "" { //if url.path is "" then return suburl's path, special for registry url return service } } diff --git a/common/url_test.go b/common/url_test.go index 835973065b..2372de520e 100644 --- a/common/url_test.go +++ b/common/url_test.go @@ -164,6 +164,7 @@ func TestURL_GetParamAndDecoded(t *testing.T) { v, _ := u.GetParamAndDecoded("rule") assert.Equal(t, rule, v) } + func TestURL_GetRawParam(t *testing.T) { u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") u.Username = "test" @@ -176,6 +177,7 @@ func TestURL_GetRawParam(t *testing.T) { assert.Equal(t, "/com.foo.BarService", u.GetRawParam("path")) assert.Equal(t, "fastjson", u.GetRawParam("serialization")) } + func TestURL_ToMap(t *testing.T) { u, _ := NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson") u.Username = "test" diff --git a/common/yaml/yaml.go b/common/yaml/yaml.go new file mode 100644 index 0000000000..6c0829bdb4 --- /dev/null +++ b/common/yaml/yaml.go @@ -0,0 +1,33 @@ +package yaml + +import ( + "io/ioutil" + "path" +) + +import ( + perrors "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +// 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.go b/config/base_config.go index 942e966eb3..787297c185 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -138,6 +138,7 @@ func getKeyPrefix(val reflect.Value) []string { return retPrefixs } + func getPtrElement(v reflect.Value) reflect.Value { if v.Kind() == reflect.Ptr { v = v.Elem() @@ -147,6 +148,7 @@ func getPtrElement(v reflect.Value) reflect.Value { } return v } + func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryConfiguration) { for i := 0; i < val.NumField(); i++ { if key := val.Type().Field(i).Tag.Get("property"); key != "-" && key != "" { @@ -300,6 +302,7 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC } } } + func (c *BaseConfig) fresh() { configList := config.GetEnvInstance().Configuration() for element := configList.Front(); element != nil; element = element.Next() { diff --git a/config/base_config_test.go b/config/base_config_test.go index d16b242092..34a8852414 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -18,6 +18,7 @@ package config import ( "fmt" + "path/filepath" "reflect" "testing" ) @@ -27,6 +28,7 @@ import ( import ( "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/yaml" "github.com/apache/dubbo-go/config_center" _ "github.com/apache/dubbo-go/config_center/apollo" ) @@ -517,3 +519,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, yaml.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 0000000000..87e835108e --- /dev/null +++ b/config/condition_router_config.go @@ -0,0 +1,48 @@ +/* + * 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" + "github.com/apache/dubbo-go/common/yaml" +) + +//RouterInit Load config file to init router config +func RouterInit(confRouterFile string) error { + fileRouterFactories := extension.GetFileRouterFactories() + bytes, err := yaml.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 0000000000..2f0a38b2fd --- /dev/null +++ b/config/condition_router_config_test.go @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "strings" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/cluster/directory" + _ "github.com/apache/dubbo-go/cluster/router/condition" +) + +const testYML = "testdata/router_config.yml" +const errorTestYML = "testdata/router_config_error.yml" + +func TestString(t *testing.T) { + + s := "a1=>a2" + s1 := "=>a2" + s2 := "a1=>" + + n := strings.SplitN(s, "=>", 2) + n1 := strings.SplitN(s1, "=>", 2) + n2 := strings.SplitN(s2, "=>", 2) + + assert.Equal(t, n[0], "a1") + assert.Equal(t, n[1], "a2") + + assert.Equal(t, n1[0], "") + assert.Equal(t, n1[1], "a2") + + assert.Equal(t, n2[0], "a1") + assert.Equal(t, n2[1], "") +} + +func TestRouterInit(t *testing.T) { + errPro := RouterInit(errorTestYML) + assert.Error(t, errPro) + + assert.Equal(t, 0, directory.GetRouterURLSet().Size()) + + errPro = RouterInit(testYML) + assert.NoError(t, errPro) + + assert.Equal(t, 1, directory.GetRouterURLSet().Size()) +} diff --git a/config/config_loader.go b/config/config_loader.go index 875d1f6ddb..437f4d7323 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -36,21 +36,26 @@ var ( metricConfig *MetricConfig applicationConfig *ApplicationConfig maxWait = 3 + confRouterFile string ) // loaded consumer & provider config from xxx.yml, and log config from xxx.xml // Namely: dubbo.consumer.xml & dubbo.provider.xml in java dubbo func init() { var ( - confConFile, confProFile string + confConFile string + confProFile string ) confConFile = os.Getenv(constant.CONF_CONSUMER_FILE_PATH) confProFile = os.Getenv(constant.CONF_PROVIDER_FILE_PATH) + confRouterFile = os.Getenv(constant.CONF_ROUTER_FILE_PATH) + if errCon := ConsumerInit(confConFile); errCon != nil { log.Printf("[consumerInit] %#v", errCon) consumerConfig = nil } + if errPro := ProviderInit(confProFile); errPro != nil { log.Printf("[providerInit] %#v", errPro) providerConfig = nil @@ -73,6 +78,13 @@ func checkApplicationName(config *ApplicationConfig) { // Load Dubbo Init func Load() { + // init router + if confRouterFile != "" { + if errPro := RouterInit(confRouterFile); errPro != nil { + log.Printf("[routerConfig init] %#v", errPro) + } + } + // reference config if consumerConfig == nil { logger.Warnf("consumerConfig is nil!") @@ -100,6 +112,7 @@ func Load() { ref.Refer(rpcService) ref.Implement(rpcService) } + //wait for invoker is available, if wait over default 3s, then panic var count int checkok := true diff --git a/config/consumer_config.go b/config/consumer_config.go index cd583954ff..c3fe12d703 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -18,8 +18,6 @@ package config import ( - "io/ioutil" - "path" "time" ) @@ -27,12 +25,12 @@ import ( "github.com/creasty/defaults" "github.com/dubbogo/getty" perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/yaml" ) ///////////////////////// @@ -90,21 +88,11 @@ 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 := yaml.UnmarshalYMLConfig(confConFile, consumerConfig) if err != nil { - return perrors.Errorf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) + return perrors.Errorf("unmarshalYmlConfig error %v", perrors.WithStack(err)) } - //set method interfaceId & interfaceName for k, v := range consumerConfig.References { //set id for reference diff --git a/config/protocol_config.go b/config/protocol_config.go index 4828d6e5bd..33de976bc6 100644 --- a/config/protocol_config.go +++ b/config/protocol_config.go @@ -38,14 +38,13 @@ func (c *ProtocolConfig) Prefix() string { } func loadProtocol(protocolsIds string, protocols map[string]*ProtocolConfig) []*ProtocolConfig { - returnProtocols := []*ProtocolConfig{} + returnProtocols := make([]*ProtocolConfig, 0, len(protocols)) for _, v := range strings.Split(protocolsIds, ",") { - for k, prot := range protocols { + for k, protocol := range protocols { if v == k { - returnProtocols = append(returnProtocols, prot) + returnProtocols = append(returnProtocols, protocol) } } - } return returnProtocols } diff --git a/config/provider_config.go b/config/provider_config.go index 7bed561d99..d8562863ed 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -17,20 +17,15 @@ package config -import ( - "io/ioutil" - "path" -) - import ( "github.com/creasty/defaults" perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/yaml" ) ///////////////////////// @@ -81,21 +76,11 @@ 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 := yaml.UnmarshalYMLConfig(confProFile, providerConfig) if err != nil { - return perrors.Errorf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) + return perrors.Errorf("unmarshalYmlConfig error %v", perrors.WithStack(err)) } - //set method interfaceId & interfaceName for k, v := range providerConfig.Services { //set id for reference diff --git a/config/reference_config.go b/config/reference_config.go index edfa17a27e..7ce0013194 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -77,7 +77,6 @@ func NewReferenceConfig(id string, ctx context.Context) *ReferenceConfig { // UnmarshalYAML ... func (c *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - type rf ReferenceConfig raw := rf{} // Put your defaults here if err := unmarshal(&raw); err != nil { @@ -101,8 +100,8 @@ func (c *ReferenceConfig) Refer(_ interface{}) { common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), ) - //1. user specified URL, could be peer-to-peer address, or register center's address. if c.Url != "" { + // 1. user specified URL, could be peer-to-peer address, or register center's address. urlStrings := gxstrings.RegSplit(c.Url, "\\s*[;]+\\s*") for _, urlStr := range urlStrings { serviceUrl, err := common.NewURL(urlStr) @@ -120,21 +119,21 @@ func (c *ReferenceConfig) Refer(_ interface{}) { newUrl := common.MergeUrl(&serviceUrl, cfgURL) c.urls = append(c.urls, newUrl) } - } } else { - //2. assemble SubURL from register center's configuration模式 + // 2. assemble SubURL from register center's configuration mode c.urls = loadRegistries(c.Registry, consumerConfig.Registries, common.CONSUMER) - //set url to regUrls + // set url to regUrls for _, regUrl := range c.urls { regUrl.SubURL = cfgURL } } + if len(c.urls) == 1 { c.invoker = extension.GetProtocol(c.urls[0].Protocol).Refer(*c.urls[0]) } else { - invokers := []protocol.Invoker{} + invokers := make([]protocol.Invoker, 0, len(c.urls)) var regUrl *common.URL for _, u := range c.urls { invokers = append(invokers, extension.GetProtocol(u.Protocol).Refer(*u)) @@ -151,7 +150,7 @@ func (c *ReferenceConfig) Refer(_ interface{}) { } } - //create proxy + // create proxy if c.Async { callback := GetCallback(c.id) c.pxy = extension.GetProxyFactory(consumerConfig.ProxyFactory).GetAsyncProxy(c.invoker, callback, cfgURL) @@ -219,7 +218,6 @@ func (c *ReferenceConfig) getUrlMap() url.Values { } return urlMap - } // GenericLoad ... diff --git a/config/registry_config_test.go b/config/registry_config_test.go index 45d38b29cc..6c2fed605d 100644 --- a/config/registry_config_test.go +++ b/config/registry_config_test.go @@ -46,6 +46,7 @@ func Test_loadRegistries(t *testing.T) { fmt.Println(urls[0]) assert.Equal(t, "127.0.0.2:2181,128.0.0.1:2181", urls[0].Location) } + func Test_loadRegistries1(t *testing.T) { target := "shanghai1" regs := map[string]*RegistryConfig{ diff --git a/config/service_config.go b/config/service_config.go index 2111838395..7d97fa4d1e 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -96,14 +96,12 @@ func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { // NewServiceConfig The only way to get a new ServiceConfig func NewServiceConfig(id string, context context.Context) *ServiceConfig { - return &ServiceConfig{ context: context, id: id, unexported: atomic.NewBool(false), exported: atomic.NewBool(false), } - } // Export ... @@ -171,10 +169,8 @@ func (c *ServiceConfig) Export() error { panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL))) } } - } return nil - } // Implement ... @@ -242,5 +238,4 @@ func (c *ServiceConfig) getUrlMap() url.Values { } return urlMap - } diff --git a/config/testdata/router_config.yml b/config/testdata/router_config.yml new file mode 100644 index 0000000000..f6b91f5da7 --- /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 0000000000..37894ac964 --- /dev/null +++ b/config/testdata/router_config_error.yml @@ -0,0 +1,6 @@ +# dubbo router yaml configure file +priority: 1 +force: true +noConditions : + - "a => b" + - "c => d" \ No newline at end of file diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go index 85dff14a1e..4dc1981784 100644 --- a/config_center/apollo/impl.go +++ b/config_center/apollo/impl.go @@ -163,6 +163,7 @@ func (c *apolloConfiguration) getAddressWithProtocolPrefix(url *common.URL) stri func (c *apolloConfiguration) Parser() parser.ConfigurationParser { return c.parser } + func (c *apolloConfiguration) SetParser(p parser.ConfigurationParser) { c.parser = p } diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go index d0b23ef2f2..18415bee3a 100644 --- a/config_center/configurator/override.go +++ b/config_center/configurator/override.go @@ -36,6 +36,7 @@ import ( func init() { extension.SetDefaultConfigurator(newConfigurator) } + func newConfigurator(url *common.URL) config_center.Configurator { return &overrideConfigurator{configuratorUrl: url} } diff --git a/config_center/configurator/override_test.go b/config_center/configurator/override_test.go index 329c598efe..c0aeb15130 100644 --- a/config_center/configurator/override_test.go +++ b/config_center/configurator/override_test.go @@ -40,8 +40,8 @@ func Test_configureVerison2p6(t *testing.T) { assert.NoError(t, err) configurator.Configure(&providerUrl) assert.Equal(t, "failfast", providerUrl.GetParam(constant.CLUSTER_KEY, "")) - } + func Test_configureVerisonOverrideAddr(t *testing.T) { url, err := common.NewURL("override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&providerAddresses=127.0.0.2:20001|127.0.0.3:20001") assert.NoError(t, err) @@ -52,8 +52,8 @@ func Test_configureVerisonOverrideAddr(t *testing.T) { assert.NoError(t, err) configurator.Configure(&providerUrl) assert.Equal(t, "failover", providerUrl.GetParam(constant.CLUSTER_KEY, "")) - } + func Test_configureVerison2p6WithIp(t *testing.T) { url, err := common.NewURL("override://127.0.0.1:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService") assert.NoError(t, err) diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go index 90cd3bbb1d..d6c3b06b32 100644 --- a/config_center/dynamic_configuration.go +++ b/config_center/dynamic_configuration.go @@ -22,6 +22,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/config_center/parser" ) @@ -73,3 +74,8 @@ func WithTimeout(time time.Duration) Option { opt.Timeout = time } } + +//GetRuleKey The format is '{interfaceName}:[version]:[group]' +func GetRuleKey(url common.URL) string { + return url.ColonSeparatedKey() +} diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index 06db101d88..d3373e249b 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package nacos import ( diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go index 96b4c9d4ac..ef63eeff6d 100644 --- a/config_center/nacos/client_test.go +++ b/config_center/nacos/client_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package nacos import ( diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go index 07bf8638d2..b4e6f1d025 100644 --- a/config_center/nacos/impl_test.go +++ b/config_center/nacos/impl_test.go @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package nacos import ( diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index 58fcdb49da..f342dc62e7 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -109,6 +109,7 @@ func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common } return allUrls, nil } + func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -154,6 +155,7 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR } return urls, nil } + func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -246,6 +248,7 @@ func getParamString(item ConfigItem) (string, error) { return retStr, nil } + func getEnabledString(item ConfigItem, config ConfiguratorConfig) string { retStr := "&enabled=" if len(item.Type) == 0 || item.Type == GeneralType { diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go index 70fb196a1e..404243d475 100644 --- a/config_center/zookeeper/impl.go +++ b/config_center/zookeeper/impl.go @@ -155,6 +155,7 @@ func (c *zookeeperDynamicConfiguration) GetRule(key string, opts ...config_cente func (c *zookeeperDynamicConfiguration) Parser() parser.ConfigurationParser { return c.parser } + func (c *zookeeperDynamicConfiguration) SetParser(p parser.ConfigurationParser) { c.parser = p } diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go index 1d62f3df86..22e15193cb 100644 --- a/config_center/zookeeper/impl_test.go +++ b/config_center/zookeeper/impl_test.go @@ -77,6 +77,7 @@ func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicC return ts, reg } + func Test_GetConfig(t *testing.T) { ts, reg := initZkData("dubbo", t) defer ts.Stop() diff --git a/doc/pic/arch/dubbo-go-arch.png b/doc/pic/arch/dubbo-go-arch.png new file mode 100644 index 0000000000..8f8f19957b Binary files /dev/null and b/doc/pic/arch/dubbo-go-arch.png differ diff --git a/filter/access_key.go b/filter/access_key.go index c9bdd4ff89..40d4157b31 100644 --- a/filter/access_key.go +++ b/filter/access_key.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package filter import ( diff --git a/filter/authenticator.go b/filter/authenticator.go index ce0547b36b..ac2c8601d4 100644 --- a/filter/authenticator.go +++ b/filter/authenticator.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package filter import ( diff --git a/filter/filter_impl/auth/accesskey_storage.go b/filter/filter_impl/auth/accesskey_storage.go index 0a2bf47cbd..5adb9d9ee3 100644 --- a/filter/filter_impl/auth/accesskey_storage.go +++ b/filter/filter_impl/auth/accesskey_storage.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/auth/accesskey_storage_test.go b/filter/filter_impl/auth/accesskey_storage_test.go index 6ab861a867..aa566b8176 100644 --- a/filter/filter_impl/auth/accesskey_storage_test.go +++ b/filter/filter_impl/auth/accesskey_storage_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/auth/consumer_sign.go b/filter/filter_impl/auth/consumer_sign.go index be86b5c74b..062744771a 100644 --- a/filter/filter_impl/auth/consumer_sign.go +++ b/filter/filter_impl/auth/consumer_sign.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( @@ -38,6 +55,7 @@ func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invo func (csf *ConsumerSignFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } + func getConsumerSignFilter() filter.Filter { return &ConsumerSignFilter{} } diff --git a/filter/filter_impl/auth/consumer_sign_test.go b/filter/filter_impl/auth/consumer_sign_test.go index 9a90970b89..b02380e28f 100644 --- a/filter/filter_impl/auth/consumer_sign_test.go +++ b/filter/filter_impl/auth/consumer_sign_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/auth/default_authenticator.go b/filter/filter_impl/auth/default_authenticator.go index 73eb9cddc0..2b8d559278 100644 --- a/filter/filter_impl/auth/default_authenticator.go +++ b/filter/filter_impl/auth/default_authenticator.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/auth/default_authenticator_test.go b/filter/filter_impl/auth/default_authenticator_test.go index 72220eec99..5b107b5960 100644 --- a/filter/filter_impl/auth/default_authenticator_test.go +++ b/filter/filter_impl/auth/default_authenticator_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/auth/provider_auth.go b/filter/filter_impl/auth/provider_auth.go index 90804934f6..0d5772e550 100644 --- a/filter/filter_impl/auth/provider_auth.go +++ b/filter/filter_impl/auth/provider_auth.go @@ -1,7 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( "context" +) + +import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" @@ -38,6 +58,7 @@ func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invo func (paf *ProviderAuthFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } + func getProviderAuthFilter() filter.Filter { return &ProviderAuthFilter{} } diff --git a/filter/filter_impl/auth/provider_auth_test.go b/filter/filter_impl/auth/provider_auth_test.go index 88d6423458..626782ae83 100644 --- a/filter/filter_impl/auth/provider_auth_test.go +++ b/filter/filter_impl/auth/provider_auth_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/auth/sign_util.go b/filter/filter_impl/auth/sign_util.go index 60698439c5..043a549a84 100644 --- a/filter/filter_impl/auth/sign_util.go +++ b/filter/filter_impl/auth/sign_util.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/auth/sign_util_test.go b/filter/filter_impl/auth/sign_util_test.go index de6154e885..a4aaf2da27 100644 --- a/filter/filter_impl/auth/sign_util_test.go +++ b/filter/filter_impl/auth/sign_util_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package auth import ( diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go index e8ff2679b0..9bc131ef89 100644 --- a/filter/filter_impl/generic_filter.go +++ b/filter/filter_impl/generic_filter.go @@ -83,6 +83,7 @@ func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _ func GetGenericFilter() filter.Filter { return &GenericFilter{} } + func struct2MapAll(obj interface{}) interface{} { if obj == nil { return obj @@ -127,6 +128,7 @@ func struct2MapAll(obj interface{}) interface{} { return obj } } + func setInMap(m map[string]interface{}, structField reflect.StructField, value interface{}) (result map[string]interface{}) { result = m if tagName := structField.Tag.Get("m"); tagName == "" { @@ -136,6 +138,7 @@ func setInMap(m map[string]interface{}, structField reflect.StructField, value i } return } + func headerAtoa(a string) (b string) { b = strings.ToLower(a[:1]) + a[1:] return diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go index 22948353fc..b082291998 100644 --- a/filter/filter_impl/generic_filter_test.go +++ b/filter/filter_impl/generic_filter_test.go @@ -88,6 +88,7 @@ func Test_struct2MapAll_Slice(t *testing.T) { assert.Equal(t, reflect.Slice, reflect.TypeOf(m["caCa"]).Kind()) assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind()) } + func Test_struct2MapAll_Map(t *testing.T) { var testData struct { AaAa string diff --git a/filter/filter_impl/hystrix_filter_test.go b/filter/filter_impl/hystrix_filter_test.go index 66c17d920c..71fc097c8b 100644 --- a/filter/filter_impl/hystrix_filter_test.go +++ b/filter/filter_impl/hystrix_filter_test.go @@ -213,6 +213,7 @@ func TestGetHystrixFilterConsumer(t *testing.T) { assert.NotNil(t, get) assert.True(t, get.(*HystrixFilter).COrP) } + func TestGetHystrixFilterProvider(t *testing.T) { get := GetHystrixFilterProvider() assert.NotNil(t, get) diff --git a/go.mod b/go.mod index 0eeef0e096..73046bc186 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e // indirect - github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac + github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible @@ -12,7 +12,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/creasty/defaults v1.3.0 - github.com/dubbogo/getty v1.3.2 + github.com/dubbogo/getty v1.3.3 github.com/dubbogo/go-zookeeper v1.0.0 github.com/dubbogo/gost v1.5.2 github.com/emicklei/go-restful/v3 v3.0.0 @@ -44,7 +44,7 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/soheilhy/cmux v0.1.4 // indirect - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.5.1 github.com/tebeka/strftime v0.1.3 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect diff --git a/go.sum b/go.sum index 31cf688f1a..d3570af483 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac h1:QKRMidg/RbdI5oaQWMb8Lxo63S+fLmsgMxsFoOCftKw= -github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200111150223-4ce8c8d0d7ac/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= +github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3 h1:1HM47ILUkLaMxLKUub+WHPncqrJGEQ0KRJzSJueMDpY= +github.com/apache/dubbo-go-hessian2 v1.3.1-0.20200302092433-6ae5479d93a3/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -102,8 +102,8 @@ github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dubbogo/getty v1.3.2 h1:l1KVSs/1CtTKbIPTrkTtBT6S9ddvmswDGoAnnl2CDpM= -github.com/dubbogo/getty v1.3.2/go.mod h1:ANbVQ9tbpZ2b0xdR8nRrgS/oXIsZAeRxzvPSOn/7mbk= +github.com/dubbogo/getty v1.3.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= +github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM= github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.5.1 h1:oG5dzaWf1KYynBaBoUIOkgT+YD0niHV6xxI0Odq7hDg= @@ -452,6 +452,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= @@ -494,7 +496,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 0765a330b5..5ec7db51af 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -18,6 +18,7 @@ package dubbo import ( + "math/rand" "strings" "sync" "time" @@ -83,6 +84,8 @@ func init() { return } setClientGrpool() + + rand.Seed(time.Now().UnixNano()) } // SetClientConf ... @@ -147,11 +150,18 @@ func NewClient(opt Options) *Client { opt.RequestTimeout = 3 * time.Second } + // make sure that client request sequence is an odd number + initSequence := uint64(rand.Int63n(time.Now().UnixNano())) + if initSequence%2 == 0 { + initSequence++ + } + c := &Client{ opts: opt, pendingResponses: new(sync.Map), conf: *clientConf, } + c.sequence.Store(initSequence) c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) return c diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index 3e50eb901d..76416b2baf 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -83,7 +83,13 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { // Unmarshal ... func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { - codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, buf.Len())) + // fix issue https://github.com/apache/dubbo-go/issues/380 + bufLen := buf.Len() + if bufLen < hessian.HEADER_LENGTH { + return perrors.WithStack(hessian.ErrHeaderNotEnough) + } + + codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) // read header err := codec.ReadHeader(&p.Header) diff --git a/protocol/dubbo/codec_test.go b/protocol/dubbo/codec_test.go index 149644a6ea..5dc71f0d08 100644 --- a/protocol/dubbo/codec_test.go +++ b/protocol/dubbo/codec_test.go @@ -18,12 +18,14 @@ package dubbo import ( + "bytes" "testing" "time" ) import ( hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -72,3 +74,10 @@ func TestDubboPackage_MarshalAndUnmarshal(t *testing.T) { assert.Equal(t, []interface{}{"a"}, pkgres.Body.([]interface{})[5]) assert.Equal(t, map[string]string{"dubbo": "2.0.2", "interface": "Service", "path": "path", "timeout": "1000", "version": "2.6"}, pkgres.Body.([]interface{})[6]) } + +func TestIssue380(t *testing.T) { + pkg := &DubboPackage{} + buf := bytes.NewBuffer([]byte("hello")) + err := pkg.Unmarshal(buf) + assert.True(t, perrors.Cause(err) == hessian.ErrHeaderNotEnough) +} diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go index ef5016e22e..09c3725710 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -128,7 +128,7 @@ func (di *DubboInvoker) Destroy() { for { if di.reqNum == 0 { di.reqNum = -1 - logger.Info("dubboInvoker is destroyed,url:{%s}", di.GetUrl().Key()) + logger.Infof("dubboInvoker is destroyed,url:{%s}", di.GetUrl().Key()) di.BaseInvoker.Destroy() if di.client != nil { di.client.Close() diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go index 430c4e49d8..0251b78a2b 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -124,6 +124,7 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { pendingResponse := h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) if pendingResponse == nil { + logger.Errorf("failed to get pending response context for response package %s", *p) return } diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index 9d381585b5..918514c267 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -78,7 +78,7 @@ func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*ge } time.Sleep(1e6) } - logger.Infof("client init ok") + logger.Debug("client init ok") c.updateActive(time.Now().Unix()) return c, nil @@ -319,9 +319,10 @@ func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPC conn, err := p.get() if err == nil && conn == nil { // create new conn - return newGettyRPCClientConn(p, protocol, addr) + rpcClientConn, err := newGettyRPCClientConn(p, protocol, addr) + return rpcClientConn, perrors.WithStack(err) } - return conn, err + return conn, perrors.WithStack(err) } func (p *gettyRPCClientPool) get() (*gettyRPCClient, error) { diff --git a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto index d68e1dd37b..e73f72b1e0 100644 --- a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto +++ b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.proto @@ -1,16 +1,19 @@ -// Copyright 2015 The gRPC Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* +Licensed to the Apache Software Foundation (ASF) under one or more +contributor license agreements. See the NOTICE file distributed with +this work for additional information regarding copyright ownership. +The ASF licenses this file to You under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ syntax = "proto3"; option java_multiple_files = true; diff --git a/protocol/rest/rest_client/resty_client.go b/protocol/rest/rest_client/resty_client.go index 88c3cc77e1..13b409817f 100644 --- a/protocol/rest/rest_client/resty_client.go +++ b/protocol/rest/rest_client/resty_client.go @@ -25,12 +25,9 @@ import ( "time" ) -import ( - perrors "github.com/pkg/errors" -) - import ( "github.com/go-resty/resty/v2" + perrors "github.com/pkg/errors" ) import ( diff --git a/protocol/rest/rest_config_initializer.go b/protocol/rest/rest_config_initializer.go index 13a463b049..a80dce98b1 100644 --- a/protocol/rest/rest_config_initializer.go +++ b/protocol/rest/rest_config_initializer.go @@ -45,7 +45,7 @@ func initConsumerRestConfig() { consumerConfigType := config.GetConsumerConfig().RestConfigType consumerConfigReader := extension.GetSingletonRestConfigReader(consumerConfigType) restConsumerConfig := consumerConfigReader.ReadConsumerConfig() - if restConsumerConfig == nil { + if restConsumerConfig == nil || len(restConsumerConfig.RestServiceConfigsMap) == 0 { return } restConsumerServiceConfigMap = make(map[string]*rest_interface.RestServiceConfig, len(restConsumerConfig.RestServiceConfigsMap)) @@ -60,7 +60,7 @@ func initProviderRestConfig() { providerConfigType := config.GetProviderConfig().RestConfigType providerConfigReader := extension.GetSingletonRestConfigReader(providerConfigType) restProviderConfig := providerConfigReader.ReadProviderConfig() - if restProviderConfig == nil { + if restProviderConfig == nil || len(restProviderConfig.RestServiceConfigsMap) == 0 { return } restProviderServiceConfigMap = make(map[string]*rest_interface.RestServiceConfig, len(restProviderConfig.RestServiceConfigsMap)) @@ -125,7 +125,7 @@ func transformMethodConfig(methodConfig *rest_interface.RestMethodConfig) *rest_ } func parseParamsString2Map(params string) (map[int]string, error) { - m := make(map[int]string) + m := make(map[int]string, 8) for _, p := range strings.Split(params, ",") { pa := strings.Split(p, ":") key, err := strconv.Atoi(pa[0]) diff --git a/protocol/rest/rest_config_reader/default_config_reader.go b/protocol/rest/rest_config_reader/default_config_reader.go index 3eb29af8f3..c0a1002409 100644 --- a/protocol/rest/rest_config_reader/default_config_reader.go +++ b/protocol/rest/rest_config_reader/default_config_reader.go @@ -18,20 +18,18 @@ package rest_config_reader import ( - "io/ioutil" "os" - "path" ) import ( perrors "github.com/pkg/errors" - "gopkg.in/yaml.v2" ) 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/common/yaml" "github.com/apache/dubbo-go/protocol/rest/rest_interface" ) @@ -52,23 +50,14 @@ func NewDefaultConfigReader() *DefaultConfigReader { func (dcr *DefaultConfigReader) ReadConsumerConfig() *rest_interface.RestConsumerConfig { confConFile := os.Getenv(constant.CONF_CONSUMER_FILE_PATH) - if confConFile == "" { - logger.Warnf("rest consumer configure(consumer) file name is nil") - return nil - } - if path.Ext(confConFile) != ".yml" { - logger.Warnf("rest consumer configure file name{%v} suffix must be .yml", confConFile) - return nil - } - confFileStream, err := ioutil.ReadFile(confConFile) - if err != nil { - logger.Warnf("ioutil.ReadFile(file:%s) = error:%v", confConFile, perrors.WithStack(err)) + if len(confConFile) == 0 { + logger.Warnf("[Rest Config] rest consumer configure(consumer) file name is nil") return nil } restConsumerConfig := &rest_interface.RestConsumerConfig{} - err = yaml.Unmarshal(confFileStream, restConsumerConfig) + err := yaml.UnmarshalYMLConfig(confConFile, restConsumerConfig) if err != nil { - logger.Warnf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) + logger.Errorf("[Rest Config] unmarshal Consumer RestYmlConfig error %v", perrors.WithStack(err)) return nil } return restConsumerConfig @@ -77,26 +66,15 @@ func (dcr *DefaultConfigReader) ReadConsumerConfig() *rest_interface.RestConsume func (dcr *DefaultConfigReader) ReadProviderConfig() *rest_interface.RestProviderConfig { confProFile := os.Getenv(constant.CONF_PROVIDER_FILE_PATH) if len(confProFile) == 0 { - logger.Warnf("rest provider configure(provider) file name is nil") - return nil - } - - if path.Ext(confProFile) != ".yml" { - logger.Warnf("rest provider configure file name{%v} suffix must be .yml", confProFile) - return nil - } - confFileStream, err := ioutil.ReadFile(confProFile) - if err != nil { - logger.Warnf("ioutil.ReadFile(file:%s) = error:%v", confProFile, perrors.WithStack(err)) + logger.Warnf("[Rest Config] rest provider configure(provider) file name is nil") return nil } restProviderConfig := &rest_interface.RestProviderConfig{} - err = yaml.Unmarshal(confFileStream, restProviderConfig) + err := yaml.UnmarshalYMLConfig(confProFile, restProviderConfig) if err != nil { - logger.Warnf("yaml.Unmarshal() = error:%v", perrors.WithStack(err)) + logger.Errorf("[Rest Config] unmarshal Provider RestYmlConfig error %v", perrors.WithStack(err)) return nil } - return restProviderConfig } diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go index 83ce07323d..446d122e6d 100644 --- a/protocol/rest/rest_invoker.go +++ b/protocol/rest/rest_invoker.go @@ -99,11 +99,10 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[string]string, error) { resMap := make(map[string]string, len(paramsMap)) for k, v := range paramsMap { - if k < len(args) && k >= 0 { - resMap[v] = fmt.Sprint(args[k]) - } else { + if k >= len(args) || k < 0 { return nil, perrors.Errorf("[Rest Invoke] Index %v is out of bundle", k) } + resMap[v] = fmt.Sprint(args[k]) } return resMap, nil } diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go index fdf941a0d5..f4f12e415f 100644 --- a/protocol/rest/rest_protocol.go +++ b/protocol/rest/rest_protocol.go @@ -46,10 +46,10 @@ func init() { type RestProtocol struct { protocol.BaseProtocol - serverMap map[string]rest_interface.RestServer - clientMap map[rest_interface.RestOptions]rest_interface.RestClient serverLock sync.Mutex + serverMap map[string]rest_interface.RestServer clientLock sync.Mutex + clientMap map[rest_interface.RestOptions]rest_interface.RestClient } func NewRestProtocol() *RestProtocol { @@ -97,33 +97,34 @@ func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { func (rp *RestProtocol) getServer(url common.URL, serverType string) rest_interface.RestServer { restServer, ok := rp.serverMap[url.Location] + if ok { + return restServer + } + _, ok = rp.ExporterMap().Load(url.ServiceKey()) if !ok { - _, ok := rp.ExporterMap().Load(url.ServiceKey()) - if !ok { - panic("[RestProtocol]" + url.ServiceKey() + "is not existing") - } - rp.serverLock.Lock() - restServer, ok = rp.serverMap[url.Location] - if !ok { - restServer = extension.GetNewRestServer(serverType) - restServer.Start(url) - rp.serverMap[url.Location] = restServer - } - rp.serverLock.Unlock() - + panic("[RestProtocol]" + url.ServiceKey() + "is not existing") + } + rp.serverLock.Lock() + restServer, ok = rp.serverMap[url.Location] + if !ok { + restServer = extension.GetNewRestServer(serverType) + restServer.Start(url) + rp.serverMap[url.Location] = restServer } + rp.serverLock.Unlock() return restServer } func (rp *RestProtocol) getClient(restOptions rest_interface.RestOptions, clientType string) rest_interface.RestClient { restClient, ok := rp.clientMap[restOptions] + if ok { + return restClient + } rp.clientLock.Lock() + restClient, ok = rp.clientMap[restOptions] if !ok { - restClient, ok = rp.clientMap[restOptions] - if !ok { - restClient = extension.GetNewRestClient(clientType, &restOptions) - rp.clientMap[restOptions] = restClient - } + restClient = extension.GetNewRestClient(clientType, &restOptions) + rp.clientMap[restOptions] = restClient } rp.clientLock.Unlock() return restClient diff --git a/protocol/rest/rest_server/go_restful_server.go b/protocol/rest/rest_server/go_restful_server.go index 9dc42f832f..f6a867d773 100644 --- a/protocol/rest/rest_server/go_restful_server.go +++ b/protocol/rest/rest_server/go_restful_server.go @@ -91,18 +91,20 @@ func (grs *GoRestfulServer) Deploy(invoker protocol.Invoker, restMethodConfig ma } -func getFunc(methodName string, invoker protocol.Invoker, argsTypes []reflect.Type, replyType reflect.Type, config *rest_interface.RestMethodConfig) func(req *restful.Request, resp *restful.Response) { +func getFunc(methodName string, invoker protocol.Invoker, argsTypes []reflect.Type, + replyType reflect.Type, config *rest_interface.RestMethodConfig) func(req *restful.Request, resp *restful.Response) { return func(req *restful.Request, resp *restful.Response) { var ( err error args []interface{} ) - if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && argsTypes[0].String() == "[]interface {}" { + if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && + argsTypes[0].String() == "[]interface {}" { args = getArgsInterfaceFromRequest(req, config) } else { args = getArgsFromRequest(req, argsTypes, config) } - result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodName, args, make(map[string]string, 0))) + result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodName, args, make(map[string]string))) if result.Error() != nil { err = resp.WriteError(http.StatusInternalServerError, result.Error()) if err != nil { @@ -137,7 +139,7 @@ func (grs *GoRestfulServer) Destroy() { } func getArgsInterfaceFromRequest(req *restful.Request, config *rest_interface.RestMethodConfig) []interface{} { - argsMap := make(map[int]interface{}) + argsMap := make(map[int]interface{}, 8) maxKey := 0 for k, v := range config.PathParamsMap { if maxKey < k { @@ -166,7 +168,6 @@ func getArgsInterfaceFromRequest(req *restful.Request, config *rest_interface.Re if maxKey < config.Body { maxKey = config.Body } - m := make(map[string]interface{}) // TODO read as a slice if err := req.ReadEntity(&m); err != nil { @@ -175,7 +176,6 @@ func getArgsInterfaceFromRequest(req *restful.Request, config *rest_interface.Re argsMap[config.Body] = m } } - args := make([]interface{}, maxKey+1) for k, v := range argsMap { if k >= 0 { diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go index 639fd559aa..13be47c98e 100644 --- a/protocol/rpc_status.go +++ b/protocol/rpc_status.go @@ -153,7 +153,7 @@ func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) { } atomic.StoreInt32(&rpcStatus.successiveRequestFailureCount, 0) } else { - atomic.StoreInt64(&rpcStatus.lastRequestFailedTimestamp, time.Now().Unix()) + atomic.StoreInt64(&rpcStatus.lastRequestFailedTimestamp, CurrentTimeMillis()) atomic.AddInt32(&rpcStatus.successiveRequestFailureCount, 1) atomic.AddInt32(&rpcStatus.failed, 1) atomic.AddInt64(&rpcStatus.failedElapsed, elapsed) @@ -167,3 +167,17 @@ func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) { func CurrentTimeMillis() int64 { return time.Now().UnixNano() / int64(time.Millisecond) } + +// Destroy is used to clean all status +func CleanAllStatus() { + delete1 := func(key interface{}, value interface{}) bool { + methodStatistics.Delete(key) + return true + } + methodStatistics.Range(delete1) + delete2 := func(key interface{}, value interface{}) bool { + serviceStatistic.Delete(key) + return true + } + serviceStatistic.Range(delete2) +} diff --git a/protocol/rpc_status_test.go b/protocol/rpc_status_test.go index ffdb3b5356..5a07f44eab 100644 --- a/protocol/rpc_status_test.go +++ b/protocol/rpc_status_test.go @@ -14,7 +14,7 @@ import ( ) func TestBeginCount(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") BeginCount(url, "test") @@ -28,7 +28,7 @@ func TestBeginCount(t *testing.T) { } func TestEndCount(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") EndCount(url, "test", 100, true) @@ -41,7 +41,7 @@ func TestEndCount(t *testing.T) { } func TestGetMethodStatus(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetMethodStatus(url, "test") @@ -50,7 +50,7 @@ func TestGetMethodStatus(t *testing.T) { } func TestGetUrlStatus(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetURLStatus(url) @@ -59,7 +59,7 @@ func TestGetUrlStatus(t *testing.T) { } func Test_beginCount0(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") status := GetURLStatus(url) @@ -68,7 +68,7 @@ func Test_beginCount0(t *testing.T) { } func Test_All(t *testing.T) { - defer destroy() + defer CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") request(url, "test", 100, false, true) @@ -129,23 +129,10 @@ func request(url common.URL, method string, elapsed int64, active, succeeded boo } func TestCurrentTimeMillis(t *testing.T) { - defer destroy() + defer CleanAllStatus() c := CurrentTimeMillis() assert.NotNil(t, c) str := strconv.FormatInt(c, 10) i, _ := strconv.ParseInt(str, 10, 64) assert.Equal(t, c, i) } - -func destroy() { - delete1 := func(key interface{}, value interface{}) bool { - methodStatistics.Delete(key) - return true - } - methodStatistics.Range(delete1) - delete2 := func(key interface{}, value interface{}) bool { - serviceStatistic.Delete(key) - return true - } - serviceStatistic.Range(delete2) -} diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 42d03e40be..a6d2cdf49b 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -24,6 +24,7 @@ import ( import ( perrors "github.com/pkg/errors" + "go.uber.org/atomic" ) import ( @@ -61,6 +62,8 @@ type registryDirectory struct { consumerConfigurationListener *consumerConfigurationListener referenceConfigurationListener *referenceConfigurationListener Options + serviceKey string + forbidden atomic.Bool } // NewRegistryDirectory ... @@ -124,14 +127,23 @@ func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) { } else if url.Protocol == constant.ROUTER_PROTOCOL || //2.for router url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY { url = nil - //TODO: router } switch res.Action { case remoting.EventTypeAdd, remoting.EventTypeUpdate: + logger.Infof("selector add service url{%s}", res.Service) + var urls []*common.URL + + for _, v := range directory.GetRouterURLSet().Values() { + urls = append(urls, v.(*common.URL)) + } + + if len(urls) > 0 { + dir.SetRouters(urls) + } + //dir.cacheService.EventTypeAdd(res.Path, dir.serviceTTL) oldInvoker = dir.cacheInvoker(url) case remoting.EventTypeDel: - //dir.cacheService.EventTypeDel(res.Path, dir.serviceTTL) oldInvoker = dir.uncacheInvoker(url) logger.Infof("selector delete service url{%s}", res.Service) default: @@ -179,6 +191,7 @@ func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker { for _, invokers := range groupInvokersMap { staticDir := directory.NewStaticDirectory(invokers) cluster := extension.GetCluster(dir.GetUrl().SubURL.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER)) + staticDir.BuildRouterChain(invokers) groupInvokersList = append(groupInvokersList, cluster.Join(staticDir)) } } @@ -215,13 +228,13 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { newUrl := common.MergeUrl(url, referenceUrl) dir.overrideUrl(newUrl) if cacheInvoker, ok := dir.cacheInvokersMap.Load(newUrl.Key()); !ok { - logger.Infof("service will be added in cache invokers: invokers url is %s!", newUrl) + logger.Debugf("service will be added in cache invokers: invokers url is %s!", newUrl) newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl) if newInvoker != nil { dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker) } } else { - logger.Infof("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) + logger.Debugf("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl) if newInvoker != nil { dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker) @@ -234,8 +247,13 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { //select the protocol invokers from the directory func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.Invoker { - //TODO:router - return dir.cacheInvokers + invokers := dir.cacheInvokers + routerChain := dir.RouterChain() + + if routerChain == nil { + return invokers + } + return routerChain.Route(invokers, dir.cacheOriginUrl, invocation) } func (dir *registryDirectory) IsAvailable() bool { diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go index 8ebd130d7d..0dde44e73c 100644 --- a/registry/directory/directory_test.go +++ b/registry/directory/directory_test.go @@ -30,6 +30,8 @@ import ( import ( "github.com/apache/dubbo-go/cluster/cluster_impl" + _ "github.com/apache/dubbo-go/cluster/router" + _ "github.com/apache/dubbo-go/cluster/router/condition" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -43,6 +45,7 @@ import ( func init() { config.SetConsumerConfig(config.ConsumerConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) } + func TestSubscribe(t *testing.T) { registryDirectory, _ := normalRegistryDir() @@ -104,7 +107,7 @@ func TestSubscribe_Group(t *testing.T) { func Test_Destroy(t *testing.T) { registryDirectory, _ := normalRegistryDir() - time.Sleep(1e9) + time.Sleep(3e9) assert.Len(t, registryDirectory.cacheInvokers, 3) assert.Equal(t, true, registryDirectory.IsAvailable()) @@ -116,11 +119,12 @@ func Test_Destroy(t *testing.T) { func Test_List(t *testing.T) { registryDirectory, _ := normalRegistryDir() - time.Sleep(1e9) + time.Sleep(4e9) assert.Len(t, registryDirectory.List(&invocation.RPCInvocation{}), 3) assert.Equal(t, true, registryDirectory.IsAvailable()) } + func Test_MergeProviderUrl(t *testing.T) { registryDirectory, mockRegistry := normalRegistryDir(true) providerUrl, _ := common.NewURL("dubbo://0.0.0.0:20000/org.apache.dubbo-go.mockService", diff --git a/registry/etcdv3/listener.go b/registry/etcdv3/listener.go index 79e3ad5145..f9b046a2c5 100644 --- a/registry/etcdv3/listener.go +++ b/registry/etcdv3/listener.go @@ -83,6 +83,7 @@ func NewConfigurationListener(reg *etcdV3Registry) *configurationListener { reg.WaitGroup().Add(1) return &configurationListener{registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32)} } + func (l *configurationListener) Process(configType *config_center.ConfigChangeEvent) { l.events <- configType } @@ -108,6 +109,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { } } } + func (l *configurationListener) Close() { l.registry.WaitGroup().Done() } diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index 748b8204d9..a7678ba4e2 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -77,6 +77,7 @@ func newRegistryProtocol() *registryProtocol { bounds: &sync.Map{}, } } + func getRegistry(regUrl *common.URL) registry.Registry { reg, err := extension.GetRegistry(regUrl.Protocol, regUrl) if err != nil { @@ -85,13 +86,14 @@ func getRegistry(regUrl *common.URL) registry.Registry { } return reg } + func (proto *registryProtocol) initConfigurationListeners() { proto.overrideListeners = &sync.Map{} proto.serviceConfigurationListeners = &sync.Map{} proto.providerConfigurationListener = newProviderConfigurationListener(proto.overrideListeners) } -func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { +func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { var registryUrl = url var serviceUrl = registryUrl.SubURL if registryUrl.Protocol == constant.REGISTRY_PROTOCOL { @@ -115,6 +117,7 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { serviceUrl.String(), err.Error()) return nil } + err = reg.Register(*serviceUrl) if err != nil { logger.Errorf("consumer service %v register registry %v error, error message is %s", @@ -131,7 +134,6 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { } func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporter { - proto.once.Do(func() { proto.initConfigurationListeners() }) @@ -172,13 +174,14 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte wrappedInvoker := newWrappedInvoker(invoker, providerUrl) cachedExporter = extension.GetProtocol(protocolwrapper.FILTER).Export(wrappedInvoker) proto.bounds.Store(key, cachedExporter) - logger.Infof("The exporter has not been cached, and will return a new exporter!") + logger.Infof("The exporter has not been cached, and will return a new exporter!") } go reg.Subscribe(overriderUrl, overrideSubscribeListener) return cachedExporter.(protocol.Exporter) } + func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { url := getProviderUrl(invoker) key := getCacheKey(url) @@ -202,12 +205,14 @@ type overrideSubscribeListener struct { func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Invoker, proto *registryProtocol) *overrideSubscribeListener { return &overrideSubscribeListener{url: overriderUrl, originInvoker: invoker, protocol: proto} } + func (nl *overrideSubscribeListener) Notify(event *registry.ServiceEvent) { if isMatched(&(event.Service), nl.url) && event.Action == remoting.EventTypeAdd { nl.configurator = extension.GetDefaultConfigurator(&(event.Service)) nl.doOverrideIfNecessary() } } + func (nl *overrideSubscribeListener) doOverrideIfNecessary() { providerUrl := getProviderUrl(nl.originInvoker) key := getCacheKey(providerUrl) @@ -276,6 +281,7 @@ func isMatched(providerUrl *common.URL, consumerUrl *common.URL) bool { consumerVersion == providerVersion) && (len(consumerClassifier) == 0 || consumerClassifier == constant.ANY_VALUE || consumerClassifier == providerClassifier) } + func isMatchCategory(category string, categories string) bool { if len(categories) == 0 { return category == constant.DEFAULT_CATEGORY @@ -287,6 +293,7 @@ func isMatchCategory(category string, categories string) bool { return strings.Contains(categories, category) } } + func getSubscribedOverrideUrl(providerUrl *common.URL) *common.URL { newUrl := providerUrl.Clone() newUrl.Protocol = constant.PROVIDER_PROTOCOL @@ -334,6 +341,7 @@ func getProviderUrl(invoker protocol.Invoker) *common.URL { //be careful params maps in url is map type return url.SubURL.Clone() } + func setProviderUrl(regURL *common.URL, providerURL *common.URL) { regURL.SubURL = providerURL } diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go index de57a0afa7..cee2a6a625 100644 --- a/registry/protocol/protocol_test.go +++ b/registry/protocol/protocol_test.go @@ -44,6 +44,7 @@ import ( func init() { config.SetProviderConfig(config.ProviderConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) } + func referNormal(t *testing.T, regProtocol *registryProtocol) { extension.SetProtocol("registry", GetProtocol) extension.SetRegistry("mock", registry.NewMockRegistry) diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index 588e0c5192..fe8e42db9f 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -56,7 +56,7 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { // Intercept the last bit index := strings.Index(eventType.Path, "/providers/") if index == -1 { - logger.Warn("Listen with no url, event.path={%v}", eventType.Path) + logger.Warnf("Listen with no url, event.path={%v}", eventType.Path) return false } url := eventType.Path[index+len("/providers/"):] diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go index f4e53dcc42..e13443d57d 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -141,6 +141,7 @@ func (r *zkRegistry) CloseAndNilClient() { r.client.Close() r.client = nil } + func (r *zkRegistry) ZkClient() *zookeeper.ZookeeperClient { return r.client } diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go index 8f9b80cd30..d9166fc8ea 100644 --- a/remoting/etcdv3/client_test.go +++ b/remoting/etcdv3/client_test.go @@ -31,6 +31,7 @@ import ( import ( "github.com/coreos/etcd/mvcc/mvccpb" perrors "github.com/pkg/errors" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "go.etcd.io/etcd/embed" "google.golang.org/grpc/connectivity" @@ -171,6 +172,10 @@ func (suite *ClientTestSuite) TestClientDone() { }() c.Wait.Wait() + + if c.Valid() == true { + suite.T().Fatal("client should be invalid then") + } } func (suite *ClientTestSuite) TestClientCreateKV() { @@ -295,13 +300,26 @@ func (suite *ClientTestSuite) TestClientWatch() { t.Fatal(err) } + events := make([]mvccpb.Event, 0) + var eCreate, eDelete mvccpb.Event + for e := range wc { for _, event := range e.Events { + events = append(events, (mvccpb.Event)(*event)) + if event.Type == mvccpb.PUT { + eCreate = (mvccpb.Event)(*event) + } + if event.Type == mvccpb.DELETE { + eDelete = (mvccpb.Event)(*event) + } t.Logf("type IsCreate %v k %s v %s", event.IsCreate(), event.Kv.Key, event.Kv.Value) } } + assert.Equal(t, 2, len(events)) + assert.Contains(t, events, eCreate) + assert.Contains(t, events, eDelete) }() for _, tc := range tests { @@ -334,25 +352,35 @@ func (suite *ClientTestSuite) TestClientRegisterTemp() { wg.Add(1) go func() { + defer wg.Done() + completePath := path.Join("scott", "wang") wc, err := observeC.watch(completePath) if err != nil { t.Fatal(err) } + events := make([]mvccpb.Event, 0) + var eCreate, eDelete mvccpb.Event + for e := range wc { for _, event := range e.Events { - + events = append(events, (mvccpb.Event)(*event)) if event.Type == mvccpb.DELETE { + eDelete = (mvccpb.Event)(*event) t.Logf("complete key (%s) is delete", completePath) - wg.Done() observeC.Close() - return + break } + eCreate = (mvccpb.Event)(*event) t.Logf("type IsCreate %v k %s v %s", event.IsCreate(), event.Kv.Key, event.Kv.Value) } } + + assert.Equal(t, 2, len(events)) + assert.Contains(t, events, eCreate) + assert.Contains(t, events, eDelete) }() _, err := c.RegisterTemp("scott", "wang") diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index f95231b374..21486aab59 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -412,7 +412,7 @@ func (z *ZookeeperClient) Create(basePath string) error { if err != nil { if err == zk.ErrNodeExists { - logger.Infof("zk.create(\"%s\") exists\n", tmpPath) + logger.Debugf("zk.create(\"%s\") exists\n", tmpPath) } else { logger.Errorf("zk.create(\"%s\") error(%v)\n", tmpPath, perrors.WithStack(err)) return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath) diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go index 97ea775652..a41f6cd323 100644 --- a/remoting/zookeeper/facade_test.go +++ b/remoting/zookeeper/facade_test.go @@ -70,6 +70,7 @@ func (r *mockFacade) Destroy() { func (r *mockFacade) RestartCallBack() bool { return true } + func (r *mockFacade) IsAvailable() bool { return true } diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 0d1d4bd27a..77aa05ee9e 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -245,7 +245,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi if err != nil { logger.Errorf("Get new node path {%v} 's content error,message is {%v}", dubboPath, perrors.WithStack(err)) } - logger.Infof("Get children!{%s}", dubboPath) + logger.Debugf("Get children!{%s}", dubboPath) if !listener.DataChange(remoting.Event{Path: dubboPath, Action: remoting.EventTypeAdd, Content: string(content)}) { continue } diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go index 43e9aca3f4..7301cd52c3 100644 --- a/remoting/zookeeper/listener_test.go +++ b/remoting/zookeeper/listener_test.go @@ -66,6 +66,7 @@ func initZkData(t *testing.T) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Even return ts, client, event } + func TestListener(t *testing.T) { changedData := ` dubbo.consumer.request_timeout=3s