diff --git a/.gitignore b/.gitignore index 568e9f24541dd6f02dd8670436fd48db481b7f21..8158b497e3620a15fc5a52e91020e1fcebd5be41 100644 --- a/.gitignore +++ b/.gitignore @@ -20,15 +20,19 @@ classes # go mod, go test vendor/ -coverage.txt - logs/ .vscode/ -coverage.txt # unit test remoting/zookeeper/zookeeper-4unittest/ config_center/zookeeper/zookeeper-4unittest/ registry/zookeeper/zookeeper-4unittest/ +metadata/report/zookeeper/zookeeper-4unittest/ registry/consul/agent* +metadata/report/consul/agent* +remoting/consul/agent* config_center/apollo/mockDubbog.properties.json + +# vim stuff +*~ +.*.sw? diff --git a/CHANGE.md b/CHANGE.md index 00b074d284971d779c84792951262879098fc18b..4a75ad3519fce9e8fe52fabc516d4a66c6f7b135 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,6 +1,56 @@ # Release Notes --- +## 1.5.0 + +### New Features +- [Application-Level Registry Model](https://github.com/apache/dubbo-go/pull/604) + - [DelegateMetadataReport & RemoteMetadataService](https://github.com/apache/dubbo-go/pull/505) + - [Nacos MetadataReport implementation](https://github.com/apache/dubbo-go/pull/522) + - [Nacos service discovery](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/nacos/service_discovery.go) + - [Zk metadata service](https://github.com/apache/dubbo-go/pull/633) + - [Zk service discovery](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/zookeeper/service_discovery.go) + - [Etcd metadata report](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/metadata/report/etcd/report.go) + - [Etcd metadata service discovery](https://github.com/apache/dubbo-go/blob/9a5990d9a9c3d5e6633c0d7d926c156416bcb931/registry/etcdv3/service_discovery.go) +- [Support grpc json protocol](https://github.com/apache/dubbo-go/pull/582) +- [Ftr: using different labels btw provider and consumer, k8s service discovery across namespaces](https://github.com/apache/dubbo-go/pull/577 ) + +### Enhancement +- [Optimize err handling ](https://github.com/apache/dubbo-go/pull/536/) +- [Add attribute method into Invocation and RpcInvocation](https://github.com/apache/dubbo-go/pull/537) +- [Optimize lock for zookeeper registry](https://github.com/apache/dubbo-go/pull/578) +- [Improve code coverage of zookeeper config center](https://github.com/apache/dubbo-go/pull/549) +- [Improve code coverage of nacos config center and configuration parser](https://github.com/apache/dubbo-go/pull/587) +- [Kubernetes as registry enhance](https://github.com/apache/dubbo-go/pull/577) +- [Optimize zk client's lock and tests](https://github.com/apache/dubbo-go/pull/601) +- [Add setInvoker method for invocation](https://github.com/apache/dubbo-go/pull/612) +- [Upgrade getty & hessian2](https://github.com/apache/dubbo-go/pull/626) +- [Optimize router design: Extract priority router](https://github.com/apache/dubbo-go/pull/630) +- [NamespaceId config for nacos](https://github.com/apache/dubbo-go/pull/641) + + +### Bugfixes +- [Fix Gitee problem](https://github.com/apache/dubbo-go/pull/590) +- [Gitee quality analyses -- common](https://github.com/apache/dubbo-go/issues/616) +- [Nacos client logDir path seperator for Windows](https://github.com/apache/dubbo-go/pull/591) +- [Fix various linter warnings](https://github.com/apache/dubbo-go/pull/624) +- [Fixed some issues in config folder that reported by sonar-qube](https://github.com/apache/dubbo-go/pull/634) +- [Zk disconnected, dubbo-go panic when subscribe](https://github.com/apache/dubbo-go/pull/613) +- [Enhancement cluster code analysis](https://github.com/apache/dubbo-go/pull/632) + +### Document & Comment +- [Add comment for common directory](https://github.com/apache/dubbo-go/pull/530) +- [Add comments for config_center](https://github.com/apache/dubbo-go/pull/545) +- [Update the comments in metrics](https://github.com/apache/dubbo-go/pull/547) +- [Add comments for config](https://github.com/apache/dubbo-go/pull/579) +- [Updated the dubbo-go-ext image](https://github.com/apache/dubbo-go/pull/581) +- [Add comment for cluster](https://github.com/apache/dubbo-go/pull/584) +- [Update the comments in filter directory](https://github.com/apache/dubbo-go/pull/586) +- [Add comment for metadata](https://github.com/apache/dubbo-go/pull/588) +- [Update the comments in protocol directory](https://github.com/apache/dubbo-go/pull/602) +- [Add comments for remoting](https://github.com/apache/dubbo-go/pull/605) +- [Update the comments in registy directory](https://github.com/apache/dubbo-go/pull/589) + ## 1.4.0 ### New Features diff --git a/NOTICE b/NOTICE index d7aa899d1cef0fba67826bebd0d587e9cc17ba5d..1120c200c997fe6befbe3f78d95e9bdb8a05a487 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -Apache Dubbo Go +Apache Dubbo-go Copyright 2018-2020 The Apache Software Foundation This product includes software developed at diff --git a/README.md b/README.md index 3f8394536f944518f8d969289147272c32f169da..2f40eaa3a65e9e64c751ba77b981bdade081c321 100644 --- a/README.md +++ b/README.md @@ -169,12 +169,40 @@ If you are willing to do some code contributions and document contributions to [ Benchmark project [dubbo-go-benchmark](https://github.com/dubbogo/dubbo-go-benchmark). -About dubbo-go benchmarking report, please refer to [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-jsonrpc). +About dubbo-go benchmarking report, please refer to [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-jsonrpc). ## [User List](https://github.com/apache/dubbo-go/issues/2) 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. - - - + +<div> +<table> + <tbody> + <tr></tr> + <tr> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://pic.c-ctrip.com/common/c_logo2013.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://user-images.githubusercontent.com/52339367/84628582-80512200-af1b-11ea-945a-c6b4b9ad31f2.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://mosn.io/images/community/tuya.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://github.com/mosn" target="_blank"> + <img width="222px" src="https://raw.githubusercontent.com/mosn/community/master/icons/png/mosn-labeled-horizontal.png"> + </a> + </td> + </tr> + <tr></tr> + </tbody> +</table> +</div> diff --git a/README_CN.md b/README_CN.md index 582c5cf04cba08d4167c87b40fd0e86a3aa2ceb0..94630da6c3b2f5e4c0c7e0fe39da4a3b4e780ba4 100644 --- a/README_CN.md +++ b/README_CN.md @@ -168,12 +168,39 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic 鎬ц兘娴嬭瘯椤圭洰鏄� [dubbo-go-benchmark](https://github.com/dubbogo/dubbo-go-benchmark)銆� -鍏充簬 dubbo-go 鎬ц兘娴嬭瘯鎶ュ憡锛岃闃呰 [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/pressure-test-report-for-jsonrpc)銆� +鍏充簬 dubbo-go 鎬ц兘娴嬭瘯鎶ュ憡锛岃闃呰 [dubbo benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-dubbo) & [jsonrpc benchmarking report](https://github.com/apache/dubbo-go/wiki/Benchmark-test-of-jsonrpc)銆� ## [User List](https://github.com/apache/dubbo-go/issues/2) 鑻ヤ綘姝e湪浣跨敤 [apache/dubbo-go](github.com/apache/dubbo-go) 涓旇涓哄叾鏈夌敤鎴栬€呭悜瀵瑰叾鍋氭敼杩涳紝璇峰繚鍒楄吹鍙镐俊鎭簬 [鐢ㄦ埛鍒楄〃](https://github.com/apache/dubbo-go/issues/2)锛屼互渚挎垜浠煡鏅撲箣銆� - - - +<div> +<table> + <tbody> + <tr></tr> + <tr> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://pic.c-ctrip.com/common/c_logo2013.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://user-images.githubusercontent.com/52339367/84628582-80512200-af1b-11ea-945a-c6b4b9ad31f2.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://mosn.io/images/community/tuya.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://github.com/mosn" target="_blank"> + <img width="222px" src="https://raw.githubusercontent.com/mosn/community/master/icons/png/mosn-labeled-horizontal.png"> + </a> + </td> + </tr> + <tr></tr> + </tbody> +</table> +</div> diff --git a/before_ut.bat b/before_ut.bat index dc51008dadaad21af6fcb6021863ff4102b0afa2..5d2b9e4682d0ac24efe4ae68be8d977933db103f 100644 --- a/before_ut.bat +++ b/before_ut.bat @@ -34,4 +34,7 @@ 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 +xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/" + +md metadata\report\zookeeper\zookeeper-4unittest\contrib\fatjar +xcopy /f "%zkJar%" "metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar/" \ No newline at end of file diff --git a/before_ut.sh b/before_ut.sh index 7ee92e57a26cbdbb1d1a0b3e792726ad5e1954f8..210e9e723ba9e2118cf642729359808b78fddb8d 100755 --- a/before_ut.sh +++ b/before_ut.sh @@ -25,13 +25,16 @@ if [ ! -f "${zkJar}" ]; then fi mkdir -p config_center/zookeeper/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} 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/ +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 +cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar + +mkdir -p metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar +cp ${zkJar} metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar \ No newline at end of file diff --git a/cluster/cluster_impl/available_cluster.go b/cluster/cluster_impl/available_cluster.go index b70a97fad2de1b267ac1c6a5f0672ff445fadcc3..ebd5767e4c320f10c8911cf9ac3f2c81deaafb0e 100644 --- a/cluster/cluster_impl/available_cluster.go +++ b/cluster/cluster_impl/available_cluster.go @@ -38,6 +38,7 @@ func NewAvailableCluster() cluster.Cluster { return &availableCluster{} } +// Join returns a baseClusterInvoker instance func (cluser *availableCluster) Join(directory cluster.Directory) protocol.Invoker { return NewAvailableClusterInvoker(directory) } diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go index 695ffcddbbce5a1c65f806b4561670d726588aaa..8121e5c0eab16b92b323fbc0e6e944231d1ed1b9 100644 --- a/cluster/cluster_impl/base_cluster_invoker_test.go +++ b/cluster/cluster_impl/base_cluster_invoker_test.go @@ -33,10 +33,15 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) +const ( + baseClusterInvokerMethodName = "getUser" + baseClusterInvokerFormat = "dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider" +) + func TestStickyNormal(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf(baseClusterInvokerFormat, i)) url.SetParam("sticky", "true") invokers = append(invokers, NewMockInvoker(url, 1)) } @@ -45,7 +50,7 @@ func TestStickyNormal(t *testing.T) { invoked := []protocol.Invoker{} tmpRandomBalance := loadbalance.NewRandomLoadBalance() - tmpInvocation := invocation.NewRPCInvocation("getUser", nil, nil) + tmpInvocation := invocation.NewRPCInvocation(baseClusterInvokerMethodName, nil, nil) result := base.doSelect(tmpRandomBalance, tmpInvocation, invokers, invoked) result1 := base.doSelect(tmpRandomBalance, tmpInvocation, invokers, invoked) assert.Equal(t, result, result1) @@ -54,7 +59,7 @@ func TestStickyNormal(t *testing.T) { func TestStickyNormalWhenError(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf(baseClusterInvokerFormat, i)) url.SetParam("sticky", "true") invokers = append(invokers, NewMockInvoker(url, 1)) } @@ -62,8 +67,8 @@ func TestStickyNormalWhenError(t *testing.T) { base.availablecheck = true invoked := []protocol.Invoker{} - result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) + result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation(baseClusterInvokerMethodName, nil, nil), invokers, invoked) invoked = append(invoked, result) - result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) + result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation(baseClusterInvokerMethodName, nil, nil), invokers, invoked) assert.NotEqual(t, result, result1) } diff --git a/cluster/cluster_impl/broadcast_cluster.go b/cluster/cluster_impl/broadcast_cluster.go index ba454af6a8553f31b72b1d30ef5f44ec7a8278d2..ea3dee921888baa8f997cd02e92ee582f09a37d5 100644 --- a/cluster/cluster_impl/broadcast_cluster.go +++ b/cluster/cluster_impl/broadcast_cluster.go @@ -39,6 +39,7 @@ func NewBroadcastCluster() cluster.Cluster { return &broadcastCluster{} } +// Join returns a baseClusterInvoker instance func (cluster *broadcastCluster) Join(directory cluster.Directory) protocol.Invoker { return newBroadcastClusterInvoker(directory) } diff --git a/cluster/cluster_impl/broadcast_cluster_invoker.go b/cluster/cluster_impl/broadcast_cluster_invoker.go index 3a97d3d9b499c011ac90cb88b6692565388411a7..b117dbb246bec6fed6ced4fb4abcdeb4a4b7cee5 100644 --- a/cluster/cluster_impl/broadcast_cluster_invoker.go +++ b/cluster/cluster_impl/broadcast_cluster_invoker.go @@ -36,6 +36,7 @@ func newBroadcastClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *broadcastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) diff --git a/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go index 432e33122c2ee599bc848ca9ab1842084da5ef68..278ac5432e7db5d8379e4aba81065ff2ab704ff5 100644 --- a/cluster/cluster_impl/failback_cluster.go +++ b/cluster/cluster_impl/failback_cluster.go @@ -39,6 +39,7 @@ func NewFailbackCluster() cluster.Cluster { return &failbackCluster{} } +// Join returns a baseClusterInvoker instance func (cluster *failbackCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailbackClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failback_cluster_invoker.go b/cluster/cluster_impl/failback_cluster_invoker.go index 46b0ff634e56c45223a5aeb5566b9b1401518960..62f48045ec6edbc19d6603509fa1ae8c2d4ce9ee 100644 --- a/cluster/cluster_impl/failback_cluster_invoker.go +++ b/cluster/cluster_impl/failback_cluster_invoker.go @@ -72,6 +72,19 @@ func newFailbackClusterInvoker(directory cluster.Directory) protocol.Invoker { return invoker } +func (invoker *failbackClusterInvoker) tryTimerTaskProc(ctx context.Context, retryTask *retryTimerTask) { + invoked := make([]protocol.Invoker, 0) + invoked = append(invoked, retryTask.lastInvoker) + + retryInvoker := invoker.doSelect(retryTask.loadbalance, retryTask.invocation, retryTask.invokers, invoked) + var result protocol.Result + result = retryInvoker.Invoke(ctx, retryTask.invocation) + if result.Error() != nil { + retryTask.lastInvoker = retryInvoker + invoker.checkRetry(retryTask, result.Error()) + } +} + func (invoker *failbackClusterInvoker) process(ctx context.Context) { invoker.ticker = time.NewTicker(time.Second * 1) for range invoker.ticker.C { @@ -91,25 +104,11 @@ func (invoker *failbackClusterInvoker) process(ctx context.Context) { } // ignore return. the get must success. - _, err = invoker.taskList.Get(1) - if err != nil { + if _, err = invoker.taskList.Get(1); err != nil { logger.Warnf("get task found err: %v\n", err) break } - - go func(retryTask *retryTimerTask) { - invoked := make([]protocol.Invoker, 0) - invoked = append(invoked, retryTask.lastInvoker) - - retryInvoker := invoker.doSelect(retryTask.loadbalance, retryTask.invocation, retryTask.invokers, invoked) - var result protocol.Result - result = retryInvoker.Invoke(ctx, retryTask.invocation) - if result.Error() != nil { - retryTask.lastInvoker = retryInvoker - invoker.checkRetry(retryTask, result.Error()) - } - }(retryTask) - + go invoker.tryTimerTaskProc(ctx, retryTask) } } } @@ -127,31 +126,29 @@ func (invoker *failbackClusterInvoker) checkRetry(retryTask *retryTimerTask, err } } +// nolint func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) - err := invoker.checkInvokers(invokers, invocation) - if err != nil { + if err := invoker.checkInvokers(invokers, invocation); err != nil { logger.Errorf("Failed to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.\n", invocation.MethodName(), invoker.GetUrl().Service(), err) return &protocol.RPCResult{} } - url := invokers[0].GetUrl() - methodName := invocation.MethodName() + //Get the service loadbalance config + url := invokers[0].GetUrl() lb := url.GetParam(constant.LOADBALANCE_KEY, constant.DEFAULT_LOADBALANCE) - //Get the service method loadbalance config if have + methodName := invocation.MethodName() if v := url.GetMethodParam(methodName, constant.LOADBALANCE_KEY, ""); v != "" { lb = v } - loadbalance := extension.GetLoadbalance(lb) + loadBalance := extension.GetLoadbalance(lb) invoked := make([]protocol.Invoker, 0, len(invokers)) - var result protocol.Result - - ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked) + ivk := invoker.doSelect(loadBalance, invocation, invokers, invoked) //DO INVOKE - result = ivk.Invoke(ctx, invocation) + result := ivk.Invoke(ctx, invocation) if result.Error() != nil { invoker.once.Do(func() { invoker.taskList = queue.New(invoker.failbackTasks) @@ -164,7 +161,7 @@ func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation pr return &protocol.RPCResult{} } - timerTask := newRetryTimerTask(loadbalance, invocation, invokers, ivk) + timerTask := newRetryTimerTask(loadBalance, invocation, invokers, ivk) invoker.taskList.Put(timerTask) logger.Errorf("Failback to invoke the method %v in the service %v, wait for retry in background. Ignored exception: %v.\n", @@ -172,7 +169,6 @@ func (invoker *failbackClusterInvoker) Invoke(ctx context.Context, invocation pr // ignore return &protocol.RPCResult{} } - return result } diff --git a/cluster/cluster_impl/failfast_cluster.go b/cluster/cluster_impl/failfast_cluster.go index ac9ec6b821c1d0333c73fae56169d5bc8256ec5b..a5ea7a05855e58b9db02f41fcfba83b52d35d8b0 100644 --- a/cluster/cluster_impl/failfast_cluster.go +++ b/cluster/cluster_impl/failfast_cluster.go @@ -39,6 +39,7 @@ func NewFailFastCluster() cluster.Cluster { return &failfastCluster{} } +// Join returns a baseClusterInvoker instance func (cluster *failfastCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailFastClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failfast_cluster_invoker.go b/cluster/cluster_impl/failfast_cluster_invoker.go index 3b4dc9b721720948cf635f57191d1e6bce023a1e..d71ef5f5a1dfed2d29f6ae4f29fb08d34aae9c5d 100644 --- a/cluster/cluster_impl/failfast_cluster_invoker.go +++ b/cluster/cluster_impl/failfast_cluster_invoker.go @@ -35,6 +35,7 @@ func newFailFastClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *failfastClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) err := invoker.checkInvokers(invokers, invocation) diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go index d30a743e034dafabad87381cdaa356e7603b74d1..254cc097e3e7f0cc70dfcff01212562bc6febe97 100644 --- a/cluster/cluster_impl/failover_cluster.go +++ b/cluster/cluster_impl/failover_cluster.go @@ -40,6 +40,7 @@ func NewFailoverCluster() cluster.Cluster { return &failoverCluster{} } +// Join returns a baseClusterInvoker instance func (cluster *failoverCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailoverClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go index 66adabd1043d6e5d770704774dda22ba9e6faebe..4260a9324dd360ac24f80e425d8542a423c8815e 100644 --- a/cluster/cluster_impl/failover_cluster_invoker.go +++ b/cluster/cluster_impl/failover_cluster_invoker.go @@ -19,6 +19,7 @@ package cluster_impl import ( "context" + "fmt" "strconv" ) @@ -44,6 +45,7 @@ func newFailoverClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( result protocol.Result @@ -91,8 +93,10 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr invokerSvc := invoker.GetUrl().Service() invokerUrl := invoker.directory.GetUrl() return &protocol.RPCResult{ - Err: perrors.Errorf("Failed to invoke the method %v in the service %v. Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. Last error is %v.", - methodName, invokerSvc, retries, providers, len(providers), len(invokers), invokerUrl, ip, constant.Version, result.Error().Error(), + Err: perrors.Wrap(result.Error(), fmt.Sprintf("Failed to invoke the method %v in the service %v. "+ + "Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. "+ + "Last error is %+v.", methodName, invokerSvc, retries, providers, len(providers), len(invokers), + invokerUrl, ip, constant.Version, result.Error().Error()), )} } diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index e05b79202cd202334db1c19421e3163ee28bac26..d3ac2c8a5ffd7fce647649c53fe343ba93999636 100644 --- a/cluster/cluster_impl/failover_cluster_test.go +++ b/cluster/cluster_impl/failover_cluster_test.go @@ -43,6 +43,7 @@ import ( // mock invoker // /////////////////////////// +// nolint type MockInvoker struct { url common.URL available bool @@ -51,6 +52,7 @@ type MockInvoker struct { successCount int } +// nolint func NewMockInvoker(url common.URL, successCount int) *MockInvoker { return &MockInvoker{ url: url, @@ -60,23 +62,28 @@ func NewMockInvoker(url common.URL, successCount int) *MockInvoker { } } +// nolint func (bi *MockInvoker) GetUrl() common.URL { return bi.url } +// nolint func (bi *MockInvoker) IsAvailable() bool { return bi.available } +// nolint func (bi *MockInvoker) IsDestroyed() bool { return bi.destroyed } +// nolint type rest struct { tried int success bool } +// nolint func (bi *MockInvoker) Invoke(c context.Context, invocation protocol.Invocation) protocol.Result { count++ var ( @@ -93,14 +100,17 @@ func (bi *MockInvoker) Invoke(c context.Context, invocation protocol.Invocation) return result } +// nolint func (bi *MockInvoker) Destroy() { logger.Infof("Destroy invoker: %v", bi.GetUrl().String()) bi.destroyed = true bi.available = false } +// nolint var count int +// nolint func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocation.RPCInvocation) protocol.Result { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failoverCluster := NewFailoverCluster() @@ -119,6 +129,7 @@ func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocat return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) } +// nolint func TestFailoverInvokeSuccess(t *testing.T) { urlParams := url.Values{} result := normalInvoke(3, urlParams) @@ -126,6 +137,7 @@ func TestFailoverInvokeSuccess(t *testing.T) { count = 0 } +// nolint func TestFailoverInvokeFail(t *testing.T) { urlParams := url.Values{} result := normalInvoke(4, urlParams) @@ -133,6 +145,7 @@ func TestFailoverInvokeFail(t *testing.T) { count = 0 } +// nolint func TestFailoverInvoke1(t *testing.T) { urlParams := url.Values{} urlParams.Set(constant.RETRIES_KEY, "3") @@ -141,6 +154,7 @@ func TestFailoverInvoke1(t *testing.T) { count = 0 } +// nolint func TestFailoverInvoke2(t *testing.T) { urlParams := url.Values{} urlParams.Set(constant.RETRIES_KEY, "2") @@ -152,6 +166,7 @@ func TestFailoverInvoke2(t *testing.T) { count = 0 } +// nolint func TestFailoverDestroy(t *testing.T) { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failoverCluster := NewFailoverCluster() diff --git a/cluster/cluster_impl/failsafe_cluster.go b/cluster/cluster_impl/failsafe_cluster.go index f708b7fb9108bdd17fec5dc68dc1e4249c8199d4..d9465c034cb9e76d2f360b395f17b23d9234c532 100644 --- a/cluster/cluster_impl/failsafe_cluster.go +++ b/cluster/cluster_impl/failsafe_cluster.go @@ -39,6 +39,7 @@ func NewFailsafeCluster() cluster.Cluster { return &failsafeCluster{} } +// Join returns a baseClusterInvoker instance func (cluster *failsafeCluster) Join(directory cluster.Directory) protocol.Invoker { return newFailsafeClusterInvoker(directory) } diff --git a/cluster/cluster_impl/failsafe_cluster_invoker.go b/cluster/cluster_impl/failsafe_cluster_invoker.go index 4d8fe27719eb71fa287fe4142d8e92ca17acfba4..27c59fff18d4a93fc693d7edbca467e0ffef549a 100644 --- a/cluster/cluster_impl/failsafe_cluster_invoker.go +++ b/cluster/cluster_impl/failsafe_cluster_invoker.go @@ -45,6 +45,7 @@ func newFailsafeClusterInvoker(directory cluster.Directory) protocol.Invoker { } } +// nolint func (invoker *failsafeClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) diff --git a/cluster/cluster_impl/forking_cluster.go b/cluster/cluster_impl/forking_cluster.go index 0e6cd26882788a1f897d0d4dc8e0d4eb0a9d4218..8c9911327527e66864200630f5a52dd5c7b9be53 100644 --- a/cluster/cluster_impl/forking_cluster.go +++ b/cluster/cluster_impl/forking_cluster.go @@ -39,6 +39,7 @@ func NewForkingCluster() cluster.Cluster { return &forkingCluster{} } +// Join returns a baseClusterInvoker instance func (cluster *forkingCluster) Join(directory cluster.Directory) protocol.Invoker { return newForkingClusterInvoker(directory) } diff --git a/cluster/cluster_impl/forking_cluster_invoker.go b/cluster/cluster_impl/forking_cluster_invoker.go index a5a3f2ec6605dfb843fab09dff0a53000bbc3298..168444881653ca38ef61a9bc8e50f2d4bc3e624c 100644 --- a/cluster/cluster_impl/forking_cluster_invoker.go +++ b/cluster/cluster_impl/forking_cluster_invoker.go @@ -44,7 +44,7 @@ func newForkingClusterInvoker(directory cluster.Directory) protocol.Invoker { } } -// Invoke ... +// nolint func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { if err := invoker.checkWhetherDestroyed(); err != nil { return &protocol.RPCResult{Err: err} diff --git a/cluster/cluster_impl/mock_cluster.go b/cluster/cluster_impl/mock_cluster.go index d887cfb45b9c92c859b1396046c1c1c73d46b295..a7fa5d4f02087d4ec8ee0e011c09b11805f79a3f 100644 --- a/cluster/cluster_impl/mock_cluster.go +++ b/cluster/cluster_impl/mock_cluster.go @@ -33,6 +33,7 @@ func NewMockCluster() cluster.Cluster { return &mockCluster{} } +// nolint func (cluster *mockCluster) Join(directory cluster.Directory) protocol.Invoker { return protocol.NewBaseInvoker(directory.GetUrl()) } diff --git a/cluster/cluster_impl/registry_aware_cluster.go b/cluster/cluster_impl/registry_aware_cluster.go index fcefa52862a39eece98dca8660e62d9ca144e955..f4c08973713e6b3736b4ca1ed0d6e5275cf61d01 100644 --- a/cluster/cluster_impl/registry_aware_cluster.go +++ b/cluster/cluster_impl/registry_aware_cluster.go @@ -34,6 +34,7 @@ func NewRegistryAwareCluster() cluster.Cluster { return ®istryAwareCluster{} } +// nolint func (cluster *registryAwareCluster) Join(directory cluster.Directory) protocol.Invoker { return newRegistryAwareClusterInvoker(directory) } diff --git a/cluster/cluster_impl/registry_aware_cluster_invoker.go b/cluster/cluster_impl/registry_aware_cluster_invoker.go index cded5bf16432e6b0c590e15b81c28369889a5f88..7840da5218090a995298e718a4bbce9d7f5480b4 100644 --- a/cluster/cluster_impl/registry_aware_cluster_invoker.go +++ b/cluster/cluster_impl/registry_aware_cluster_invoker.go @@ -36,6 +36,7 @@ func newRegistryAwareClusterInvoker(directory cluster.Directory) protocol.Invoke } } +// nolint func (invoker *registryAwareClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { invokers := invoker.directory.List(invocation) //First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'default' key. diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 7865b807ad4bedfd045aadffbfdbf2f19186e293..8a7e0c0e8359c69faf0e504adeb1778c38a08e04 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -22,7 +22,6 @@ import ( ) import ( - "github.com/dubbogo/gost/container/set" "go.uber.org/atomic" ) @@ -35,8 +34,6 @@ import ( "github.com/apache/dubbo-go/common/logger" ) -var routerURLSet = gxset.NewSet() - // BaseDirectory Abstract implementation of Directory: Invoker list returned from this Directory's list method have been filtered by Routers type BaseDirectory struct { url *common.URL @@ -120,14 +117,3 @@ func (dir *BaseDirectory) Destroy(doDestroy func()) { 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/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 84fbb268c7a8ec32f007a734e2d6da56ef3c6d25..85eb96417c5c1f1946962a87f6b889c2ed09c26c 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -38,9 +38,9 @@ import ( ) const ( - // ConsistentHash ... + // ConsistentHash consistent hash ConsistentHash = "consistenthash" - // HashNodes ... + // HashNodes hash nodes HashNodes = "hash.nodes" // HashArguments key of hash arguments in url HashArguments = "hash.arguments" @@ -157,6 +157,7 @@ func (c *ConsistentHashSelector) selectForKey(hash uint32) protocol.Invoker { return c.virtualInvokers[c.keys[idx]] } +// nolint func (c *ConsistentHashSelector) hash(digest [16]byte, i int) uint32 { return uint32((digest[3+i*4]&0xFF)<<24) | uint32((digest[2+i*4]&0xFF)<<16) | uint32((digest[1+i*4]&0xFF)<<8) | uint32(digest[i*4]&0xFF)&0xFFFFFFF diff --git a/cluster/loadbalance/consistent_hash_test.go b/cluster/loadbalance/consistent_hash_test.go index a44293172c6e2c96bd098a19306f69260b713689..9f22d39dc46243dddda89151e07dbea39ab933fb 100644 --- a/cluster/loadbalance/consistent_hash_test.go +++ b/cluster/loadbalance/consistent_hash_test.go @@ -18,6 +18,7 @@ package loadbalance import ( + "fmt" "testing" ) @@ -32,6 +33,19 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) +const ( + ip = "192.168.1.0" + port8080 = 8080 + port8082 = 8082 + + url8080Short = "dubbo://192.168.1.0:8080" + url8081Short = "dubbo://192.168.1.0:8081" + url20000 = "dubbo://192.168.1.0:20000/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1" + url8080 = "dubbo://192.168.1.0:8080/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1" + url8081 = "dubbo://192.168.1.0:8081/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1" + url8082 = "dubbo://192.168.1.0:8082/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1" +) + func TestConsistentHashSelectorSuite(t *testing.T) { suite.Run(t, new(consistentHashSelectorSuite)) } @@ -43,8 +57,7 @@ type consistentHashSelectorSuite struct { func (s *consistentHashSelectorSuite) SetupTest() { var invokers []protocol.Invoker - url, _ := common.NewURL( - "dubbo://192.168.1.0:20000/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + url, _ := common.NewURL(url20000) invokers = append(invokers, protocol.NewBaseInvoker(url)) s.selector = newConsistentHashSelector(invokers, "echo", 999944) } @@ -55,14 +68,14 @@ func (s *consistentHashSelectorSuite) TestToKey() { } func (s *consistentHashSelectorSuite) TestSelectForKey() { - url1, _ := common.NewURL("dubbo://192.168.1.0:8080") - url2, _ := common.NewURL("dubbo://192.168.1.0:8081") + url1, _ := common.NewURL(url8080Short) + url2, _ := common.NewURL(url8081Short) s.selector.virtualInvokers = make(map[uint32]protocol.Invoker) s.selector.virtualInvokers[99874] = protocol.NewBaseInvoker(url1) s.selector.virtualInvokers[9999945] = protocol.NewBaseInvoker(url2) s.selector.keys = []uint32{99874, 9999945} result := s.selector.selectForKey(9999944) - s.Equal(result.GetUrl().String(), "dubbo://192.168.1.0:8081?") + s.Equal(result.GetUrl().String(), url8081Short+"?") } func TestConsistentHashLoadBalanceSuite(t *testing.T) { @@ -83,11 +96,11 @@ type consistentHashLoadBalanceSuite struct { func (s *consistentHashLoadBalanceSuite) SetupTest() { var err error - s.url1, err = common.NewURL("dubbo://192.168.1.0:8080/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + s.url1, err = common.NewURL(url8080) s.NoError(err) - s.url2, err = common.NewURL("dubbo://192.168.1.0:8081/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + s.url2, err = common.NewURL(url8081) s.NoError(err) - s.url3, err = common.NewURL("dubbo://192.168.1.0:8082/org.apache.demo.HelloService?methods.echo.hash.arguments=0,1") + s.url3, err = common.NewURL(url8082) s.NoError(err) s.invoker1 = protocol.NewBaseInvoker(s.url1) @@ -101,9 +114,9 @@ func (s *consistentHashLoadBalanceSuite) SetupTest() { func (s *consistentHashLoadBalanceSuite) TestSelect() { args := []interface{}{"name", "password", "age"} invoker := s.lb.Select(s.invokers, invocation.NewRPCInvocation("echo", args, nil)) - s.Equal(invoker.GetUrl().Location, "192.168.1.0:8080") + s.Equal(invoker.GetUrl().Location, fmt.Sprintf("%s:%d", ip, port8080)) args = []interface{}{"ok", "abc"} invoker = s.lb.Select(s.invokers, invocation.NewRPCInvocation("echo", args, nil)) - s.Equal(invoker.GetUrl().Location, "192.168.1.0:8082") + s.Equal(invoker.GetUrl().Location, fmt.Sprintf("%s:%d", ip, port8082)) } diff --git a/cluster/loadbalance/least_active.go b/cluster/loadbalance/least_active.go index 37ad91c3ed6b44370820a989b7af8ccaa82c48a2..87767359a94c29c9aa83ae1fa5b042d7bac2548b 100644 --- a/cluster/loadbalance/least_active.go +++ b/cluster/loadbalance/least_active.go @@ -46,6 +46,7 @@ func NewLeastActiveLoadBalance() cluster.LoadBalance { return &leastActiveLoadBalance{} } +// Select gets invoker based on least active load balancing strategy func (lb *leastActiveLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { count := len(invokers) if count == 0 { diff --git a/cluster/loadbalance/random_test.go b/cluster/loadbalance/random_test.go index 88392de52c93579dd4def3da2d60b415b601b21e..b94d7da43d5bd42e6798fca750c8616830a8df8f 100644 --- a/cluster/loadbalance/random_test.go +++ b/cluster/loadbalance/random_test.go @@ -36,18 +36,24 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) +const ( + tmpUrl = "dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider" + tmpUrlFormat = "dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider" + tmpIp = "192.168.1.100" +) + func TestRandomlbSelect(t *testing.T) { randomlb := NewRandomLoadBalance() invokers := []protocol.Invoker{} - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", 0)) + url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, 0)) invokers = append(invokers, protocol.NewBaseInvoker(url)) i := randomlb.Select(invokers, &invocation.RPCInvocation{}) assert.True(t, i.GetUrl().URLEqual(url)) for i := 1; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } randomlb.Select(invokers, &invocation.RPCInvocation{}) @@ -58,13 +64,13 @@ func TestRandomlbSelectWeight(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } urlParams := url.Values{} urlParams.Set("methods.test."+constant.WEIGHT_KEY, "10000000000000") - urll, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams)) + urll, _ := common.NewURL(tmpUrl, common.WithParams(urlParams)) invokers = append(invokers, protocol.NewBaseInvoker(urll)) ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test")) @@ -72,7 +78,7 @@ func TestRandomlbSelectWeight(t *testing.T) { var selected float64 for i := 0; i < 10000; i++ { s := randomlb.Select(invokers, ivc) - if s.GetUrl().Ip == "192.168.1.100" { + if s.GetUrl().Ip == tmpIp { selected++ } selectedInvoker = append(selectedInvoker, s) @@ -89,13 +95,13 @@ func TestRandomlbSelectWarmup(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) + url, _ := common.NewURL(fmt.Sprintf(tmpUrlFormat, i)) invokers = append(invokers, protocol.NewBaseInvoker(url)) } urlParams := url.Values{} urlParams.Set(constant.REMOTE_TIMESTAMP_KEY, strconv.FormatInt(time.Now().Add(time.Minute*(-9)).Unix(), 10)) - urll, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.100:20000/com.ikurento.user.UserProvider"), common.WithParams(urlParams)) + urll, _ := common.NewURL(tmpUrl, common.WithParams(urlParams)) invokers = append(invokers, protocol.NewBaseInvoker(urll)) ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test")) @@ -103,7 +109,7 @@ func TestRandomlbSelectWarmup(t *testing.T) { var selected float64 for i := 0; i < 10000; i++ { s := randomlb.Select(invokers, ivc) - if s.GetUrl().Ip == "192.168.1.100" { + if s.GetUrl().Ip == tmpIp { selected++ } selectedInvoker = append(selectedInvoker, s) diff --git a/cluster/loadbalance/round_robin.go b/cluster/loadbalance/round_robin.go index c44b239dbcbcc744f47ca3c97128f92567e32a78..51a76da99818063b6f13de1c007ccbc3709e1701 100644 --- a/cluster/loadbalance/round_robin.go +++ b/cluster/loadbalance/round_robin.go @@ -31,12 +31,12 @@ import ( ) const ( - // RoundRobin ... + // RoundRobin load balancing way RoundRobin = "roundrobin" - // COMPLETE ... + // nolint COMPLETE = 0 - // UPDATING ... + // nolint UPDATING = 1 ) @@ -59,6 +59,7 @@ func NewRoundRobinLoadBalance() cluster.LoadBalance { return &roundRobinLoadBalance{} } +// Select gets invoker based on round robin load balancing strategy func (lb *roundRobinLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { count := len(invokers) if count == 0 { diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go index b4e4e1c0bd6ec02fdc19ab0fa2de0f4de80457fd..c1f723525f5307e7732f0ea1ecc27eca7ba09c8d 100644 --- a/cluster/router/chain/chain_test.go +++ b/cluster/router/chain/chain_test.go @@ -20,7 +20,6 @@ package chain import ( "encoding/base64" "fmt" - "strconv" "testing" "time" ) @@ -43,9 +42,22 @@ import ( ) const ( - path = "/dubbo/config/dubbo/test-condition.condition-router" - zkPrefix = "zookeeper://127.0.0.1:" - anyUrl = "condition://0.0.0.0/com.foo.BarService" + localIP = "127.0.0.1" + test1234IP = "1.2.3.4" + test1111IP = "1.1.1.1" + test0000IP = "0.0.0.0" + port20000 = 20000 + + path = "/dubbo/config/dubbo/test-condition.condition-router" + zkFormat = "zookeeper://%s:%d" + consumerFormat = "consumer://%s/com.foo.BarService" + dubboForamt = "dubbo://%s:%d/com.foo.BarService" + anyUrlFormat = "condition://%s/com.foo.BarService" + zk = "zookeeper" + applicationKey = "test-condition" + applicationField = "application" + forceField = "force" + forceValue = "true" ) func TestNewRouterChain(t *testing.T) { @@ -66,14 +78,14 @@ conditions: defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) assert.NotNil(t, configuration) - chain, err := NewRouterChain(getRouteUrl("test-condition")) + chain, err := NewRouterChain(getRouteUrl(applicationKey)) assert.Nil(t, err) assert.Equal(t, 1, len(chain.routers)) appRouter := chain.routers[0].(*condition.AppRouter) @@ -116,15 +128,15 @@ conditions: defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) - chain, err := NewRouterChain(getConditionRouteUrl("test-condition")) + chain, err := NewRouterChain(getConditionRouteUrl(applicationKey)) assert.Nil(t, err) assert.Equal(t, 2, len(chain.routers)) - url := getConditionRouteUrl("test-condition") + url := getConditionRouteUrl(applicationKey) assert.NotNil(t, url) factory := extension.GetRouterFactory(url.Protocol) r, err := factory.NewPriorityRouter(url) @@ -142,22 +154,22 @@ func TestRouterChainRoute(t *testing.T) { defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) - chain, err := NewRouterChain(getConditionRouteUrl("test-condition")) + chain, err := NewRouterChain(getConditionRouteUrl(applicationKey)) assert.Nil(t, err) assert.Equal(t, 1, len(chain.routers)) - url := getConditionRouteUrl("test-condition") + url := getConditionRouteUrl(applicationKey) assert.NotNil(t, url) invokers := []protocol.Invoker{} - dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService")) + dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) - targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService")) + targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} finalInvokers := chain.Route(invokers, &targetURL, inv) @@ -182,46 +194,46 @@ conditions: defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) - chain, err := NewRouterChain(getConditionRouteUrl("test-condition")) + chain, err := NewRouterChain(getConditionRouteUrl(applicationKey)) 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")) + dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) - targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService")) + targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} finalInvokers := chain.Route(invokers, &targetURL, inv) assert.Equal(t, 0, len(finalInvokers)) } -func TestRouterChain_Route_NoRoute(t *testing.T) { +func TestRouterChainRouteNoRoute(t *testing.T) { ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) - chain, err := NewRouterChain(getConditionNoRouteUrl("test-condition")) + chain, err := NewRouterChain(getConditionNoRouteUrl(applicationKey)) assert.Nil(t, err) assert.Equal(t, 1, len(chain.routers)) - url := getConditionRouteUrl("test-condition") + url := getConditionRouteUrl(applicationKey) assert.NotNil(t, url) invokers := []protocol.Invoker{} - dubboURL, _ := common.NewURL(fmt.Sprintf("dubbo://1.2.3.4:20000/com.foo.BarService")) + dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) - targetURL, _ := common.NewURL(fmt.Sprintf("consumer://1.1.1.1/com.foo.BarService")) + targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} finalInvokers := chain.Route(invokers, &targetURL, inv) @@ -229,26 +241,26 @@ func TestRouterChain_Route_NoRoute(t *testing.T) { } func getConditionNoRouteUrl(applicationKey string) *common.URL { - url, _ := common.NewURL(anyUrl) - url.AddParam("application", applicationKey) - url.AddParam("force", "true") + url, _ := common.NewURL(fmt.Sprintf(anyUrlFormat, test0000IP)) + url.AddParam(applicationField, applicationKey) + url.AddParam(forceField, forceValue) 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(anyUrl) - url.AddParam("application", applicationKey) - url.AddParam("force", "true") + url, _ := common.NewURL(fmt.Sprintf(anyUrlFormat, test0000IP)) + url.AddParam(applicationField, applicationKey) + url.AddParam(forceField, forceValue) 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(anyUrl) - url.AddParam("application", applicationKey) - url.AddParam("force", "true") + url, _ := common.NewURL(fmt.Sprintf(anyUrlFormat, test0000IP)) + url.AddParam(applicationField, applicationKey) + url.AddParam(forceField, forceValue) return &url } diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go index f37a483e8468bc57d3ce1e73172ccf9a05bc29f0..8b38f2dd6136b4d31f46e7214c0ad1359537b198 100644 --- a/cluster/router/condition/app_router_test.go +++ b/cluster/router/condition/app_router_test.go @@ -18,7 +18,7 @@ package condition import ( - "strconv" + "fmt" "testing" "time" ) @@ -31,6 +31,7 @@ import ( 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/extension" "github.com/apache/dubbo-go/config_center" "github.com/apache/dubbo-go/remoting" @@ -38,7 +39,15 @@ import ( ) const ( - path = "/dubbo/config/dubbo/test-condition.condition-router" + routerPath = "/dubbo/config/dubbo/test-condition.condition-router" + routerLocalIP = "127.0.0.1" + routerZk = "zookeeper" + routerKey = "test-condition" +) + +var ( + zkFormat = "zookeeper://%s:%d" + conditionFormat = "condition://%s/com.foo.BarService" ) func TestNewAppRouter(t *testing.T) { @@ -51,22 +60,22 @@ conditions: ` ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create(path) + err = z.Create(routerPath) assert.NoError(t, err) - _, err = z.Conn.Set(path, []byte(testYML), 0) + _, err = z.Conn.Set(routerPath, []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) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) assert.NotNil(t, configuration) - appRouteURL := getAppRouteURL("test-condition") + appRouteURL := getAppRouteURL(routerKey) appRouter, err := NewAppRouter(appRouteURL) assert.Nil(t, err) assert.NotNil(t, appRouter) @@ -97,22 +106,22 @@ conditions: ` ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create(path) + err = z.Create(routerPath) assert.NoError(t, err) - _, err = z.Conn.Set(path, []byte(testYML), 0) + _, err = z.Conn.Set(routerPath, []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) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) assert.NotNil(t, configuration) - appRouteURL := getAppRouteURL("test-condition") + appRouteURL := getAppRouteURL(routerKey) appRouter, err := NewAppRouter(appRouteURL) assert.Nil(t, err) assert.NotNil(t, appRouter) @@ -134,22 +143,22 @@ conditions: ` ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create(path) + err = z.Create(routerPath) assert.NoError(t, err) - _, err = z.Conn.Set(path, []byte(testYML), 0) + _, err = z.Conn.Set(routerPath, []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) + zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port)) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) assert.NotNil(t, configuration) - appRouteURL := getAppRouteURL("test-condition") + appRouteURL := getAppRouteURL(routerKey) appRouter, err := NewAppRouter(appRouteURL) assert.Nil(t, err) assert.NotNil(t, appRouter) @@ -175,7 +184,7 @@ conditions: } func getAppRouteURL(applicationKey string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL(fmt.Sprintf(conditionFormat, constant.ANYHOST_VALUE)) url.AddParam("application", applicationKey) url.AddParam("force", "true") return &url diff --git a/cluster/router/condition/factory_test.go b/cluster/router/condition/factory_test.go index d080a46cc736c71f22e9e3d7f8b236b4b5307362..0f61b39fc71af3aaeffc731974a0fa997503693e 100644 --- a/cluster/router/condition/factory_test.go +++ b/cluster/router/condition/factory_test.go @@ -33,12 +33,21 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) -const anyUrl = "condition://0.0.0.0/com.foo.BarService" +const ( + factory1111Ip = "1.1.1.1" + factoryUrlFormat = "condition://%s/com.foo.BarService" + factoryDubboFormat = "dubbo://%s:20880/com.foo.BarService" + factoryConsumerMethodFormat = "consumer://%s/com.foo.BarService?methods=getFoo" + factory333URL = "dubbo://10.20.3.3:20880/com.foo.BarService" + factoryConsumerFormat = "consumer://%s/com.foo.BarService" + factoryHostIp1234Format = "host = %s => host = 1.2.3.4" +) type MockInvoker struct { url common.URL @@ -61,21 +70,21 @@ func (bi *MockInvoker) GetUrl() common.URL { } func getRouteUrl(rule string) *common.URL { - url, _ := common.NewURL(anyUrl) + url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE)) url.AddParam("rule", rule) url.AddParam("force", "true") return &url } func getRouteUrlWithForce(rule, force string) *common.URL { - url, _ := common.NewURL(anyUrl) + url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE)) url.AddParam("rule", rule) url.AddParam("force", force) return &url } func getRouteUrlWithNoForce(rule string) *common.URL { - url, _ := common.NewURL(anyUrl) + url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE)) url.AddParam("rule", rule) return &url } @@ -118,11 +127,11 @@ func (bi *MockInvoker) Destroy() { bi.available = false } -func TestRouteMatchWhen(t *testing.T) { +func TestRoute_matchWhen(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4")) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - cUrl, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService") + cUrl, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, factory1111Ip)) 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")) @@ -151,12 +160,12 @@ func TestRouteMatchWhen(t *testing.T) { assert.Equal(t, true, matchWhen6) } -func TestRouteMatchFilter(t *testing.T) { +func TestRoute_matchFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() t.Logf("The local ip is %s", localIP) url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson") - url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) + url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) invokers := []protocol.Invoker{NewMockInvoker(url1, 1), NewMockInvoker(url2, 2), NewMockInvoker(url3, 3)} rule1 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.3")) rule2 := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 10.20.3.* & host != 10.20.3.3")) @@ -170,7 +179,7 @@ func TestRouteMatchFilter(t *testing.T) { router4, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule4)) router5, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule5)) router6, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule6)) - cUrl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + cUrl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) fileredInvokers1 := router1.Route(invokers, &cUrl, &invocation.RPCInvocation{}) fileredInvokers2 := router2.Route(invokers, &cUrl, &invocation.RPCInvocation{}) fileredInvokers3 := router3.Route(invokers, &cUrl, &invocation.RPCInvocation{}) @@ -186,22 +195,22 @@ func TestRouteMatchFilter(t *testing.T) { } -func TestRouteMethodRoute(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().NewPriorityRouter(getRouteUrl(rule)) url, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo") matchWhen := router.(*ConditionRouter).MatchWhen(&url, inv) assert.Equal(t, true, matchWhen) - url1, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") + url1, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv) assert.Equal(t, true, matchWhen) - url2, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=getFoo") + url2, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) rule2 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host!=1.1.1.1 => host = 1.2.3.4")) router2, _ := newConditionRouterFactory().NewPriorityRouter(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") + url3, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) rule3 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host=1.1.1.1 => host = 1.2.3.4")) router3, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule3)) matchWhen = router3.(*ConditionRouter).MatchWhen(&url3, inv) @@ -209,31 +218,31 @@ func TestRouteMethodRoute(t *testing.T) { } -func TestRouteReturnFalse(t *testing.T) { +func TestRoute_ReturnFalse(t *testing.T) { url, _ := common.NewURL("") localIP, _ := gxnet.GetLocalIP() invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false")) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } -func TestRouteReturnEmpty(t *testing.T) { +func TestRoute_ReturnEmpty(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url, _ := common.NewURL("") invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => ")) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) } -func TestRouteReturnAll(t *testing.T) { +func TestRoute_ReturnAll(t *testing.T) { localIP, _ := gxnet.GetLocalIP() urlString := "dubbo://" + localIP + "/com.foo.BarService" dubboURL, _ := common.NewURL(urlString) @@ -243,24 +252,24 @@ func TestRouteReturnAll(t *testing.T) { 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") + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } -func TestRouteHostFilter(t *testing.T) { +func TestRoute_HostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL(factory333URL) + url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) + url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) @@ -268,18 +277,18 @@ func TestRouteHostFilter(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRouteEmptyHostFilter(t *testing.T) { +func TestRoute_Empty_HostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL(factory333URL) + url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) + url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP)) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) @@ -287,18 +296,18 @@ func TestRouteEmptyHostFilter(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRouteFalseHostFilter(t *testing.T) { +func TestRoute_False_HostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL(factory333URL) + url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) + url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) @@ -306,18 +315,18 @@ func TestRouteFalseHostFilter(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRoutePlaceholder(t *testing.T) { +func TestRoute_Placeholder(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL(factory333URL) + url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) + url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host")) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 2, len(fileredInvokers)) @@ -325,35 +334,35 @@ func TestRoutePlaceholder(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRouteNoForce(t *testing.T) { +func TestRoute_NoForce(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL(factory333URL) + url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) + url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} - rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP))) + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithNoForce(rule)) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, invokers, fileredInvokers) } -func TestRouteForce(t *testing.T) { +func TestRoute_Force(t *testing.T) { localIP, _ := gxnet.GetLocalIP() - url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") - url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) - url3, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) + url1, _ := common.NewURL(factory333URL) + url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) + url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) invoker1 := NewMockInvoker(url1, 1) invoker2 := NewMockInvoker(url2, 2) invoker3 := NewMockInvoker(url3, 3) invokers := []protocol.Invoker{invoker1, invoker2, invoker3} inv := &invocation.RPCInvocation{} - rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = 1.2.3.4")) - curl, _ := common.NewURL("consumer://" + localIP + "/com.foo.BarService") + rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP))) + curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithForce(rule, "true")) fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) assert.Equal(t, 0, len(fileredInvokers)) diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index b2c876690043d18a1a9e746fee13f06c77a0de03..eabdf1c263446140b359b3e791238b020cecb50c 100644 --- a/cluster/router/condition/file.go +++ b/cluster/router/condition/file.go @@ -77,10 +77,7 @@ func (f *FileConditionRouter) URL() common.URL { } func parseCondition(conditions []string) string { - var ( - when string - then string - ) + var when, then string for _, condition := range conditions { condition = strings.Trim(condition, " ") if strings.Contains(condition, "=>") { @@ -101,10 +98,7 @@ func parseCondition(conditions []string) string { then = provider } } - } - } - return strings.Join([]string{when, then}, " => ") } diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 0267a3c7a462acb43f84ccb4701247147699804a..40a251573f5e73d40032972313565d98b288b1b1 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -181,9 +181,7 @@ func parseRule(rule string) (map[string]MatchPair, error) { return condition, nil } - var ( - pair MatchPair - ) + var pair MatchPair values := gxset.NewSet() matches := routerPatternReg.FindAllSubmatch([]byte(rule), -1) for _, groups := range matches { diff --git a/cluster/router/healthcheck/default_health_check_test.go b/cluster/router/healthcheck/default_health_check_test.go index 8a95d9a7e8dffdc3f30f94c76274a729837fc133..5d35ae8e486e3f7b29b2a68a3864ef806a1053c7 100644 --- a/cluster/router/healthcheck/default_health_check_test.go +++ b/cluster/router/healthcheck/default_health_check_test.go @@ -18,6 +18,7 @@ package healthcheck import ( + "fmt" "math" "testing" ) @@ -32,10 +33,16 @@ import ( "github.com/apache/dubbo-go/protocol" ) -func TestDefaultHealthChecker_IsHealthy(t *testing.T) { +const ( + healthCheckDubbo1010IP = "192.168.10.10" + healthCheckDubbo1011IP = "192.168.10.11" + healthCheckMethodTest = "test" + healthCheckDubboUrlFormat = "dubbo://%s:20000/com.ikurento.user.UserProvider" +) +func TestDefaultHealthCheckerIsHealthy(t *testing.T) { defer protocol.CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) hc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) invoker := NewMockInvoker(url) healthy := hc.IsHealthy(invoker) @@ -45,7 +52,7 @@ func TestDefaultHealthChecker_IsHealthy(t *testing.T) { 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) + request(url, healthCheckMethodTest, 0, true, false) } hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker) healthy = hc.IsHealthy(invoker) @@ -54,7 +61,7 @@ func TestDefaultHealthChecker_IsHealthy(t *testing.T) { // 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) + request(url, healthCheckMethodTest, 0, false, false) } url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000") @@ -63,18 +70,18 @@ func TestDefaultHealthChecker_IsHealthy(t *testing.T) { assert.False(t, hc.IsHealthy(invoker)) // reset successive failed count and go to healthy - request(url, "test", 0, false, true) + request(url, healthCheckMethodTest, 0, false, true) healthy = hc.IsHealthy(invoker) assert.True(t, hc.IsHealthy(invoker)) } -func TestDefaultHealthChecker_getCircuitBreakerSleepWindowTime(t *testing.T) { +func TestDefaultHealthCheckerGetCircuitBreakerSleepWindowTime(t *testing.T) { defer protocol.CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) // Increase the number of failed requests for i := 0; i < 100; i++ { - request(url, "test", 1, false, false) + request(url, healthCheckMethodTest, 1, false, false) } sleepWindowTime := defaultHc.getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url)) assert.True(t, sleepWindowTime == constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS) @@ -84,48 +91,48 @@ func TestDefaultHealthChecker_getCircuitBreakerSleepWindowTime(t *testing.T) { 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") + url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1011IP)) 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) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 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) { +func TestDefaultHealthCheckerGetCircuitBreakerTimeout(t *testing.T) { defer protocol.CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) 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) + url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1011IP)) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 1, false, false) + request(url1, healthCheckMethodTest, 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) { +func TestDefaultHealthCheckerIsCircuitBreakerTripped(t *testing.T) { defer protocol.CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) 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) + request(url, healthCheckMethodTest, 1, false, false) } tripped = defaultHc.isCircuitBreakerTripped(protocol.GetURLStatus(url)) assert.True(t, tripped) @@ -134,13 +141,13 @@ func TestDefaultHealthChecker_isCircuitBreakerTripped(t *testing.T) { func TestNewDefaultHealthChecker(t *testing.T) { defer protocol.CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) 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, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) url1.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "10") url1.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") nondefaultHc := NewDefaultHealthChecker(&url1).(*DefaultHealthChecker) diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go index c3a26a93896e185f0dea3732ca5afcf7687ad5ea..e80fd4c4d38dbb1c0f2b14ba1e22971249bc54b6 100644 --- a/cluster/router/healthcheck/factory_test.go +++ b/cluster/router/healthcheck/factory_test.go @@ -31,34 +31,43 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// nolint type MockInvoker struct { url common.URL } +// nolint func NewMockInvoker(url common.URL) *MockInvoker { return &MockInvoker{ url: url, } } +// nolint func (bi *MockInvoker) GetUrl() common.URL { return bi.url } + +// nolint func (bi *MockInvoker) IsAvailable() bool { return true } +// nolint func (bi *MockInvoker) IsDestroyed() bool { return true } +// nolint func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result { return nil } +// nolint func (bi *MockInvoker) Destroy() { } +// nolint func TestHealthCheckRouteFactory(t *testing.T) { factory := newHealthCheckRouteFactory() assert.NotNil(t, factory) diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go index 7bfffea705bfedade9d1d13ac7e9c380651335dd..d5862fb884114bac0ea2ec9ee8926baac57d5ba6 100644 --- a/cluster/router/healthcheck/health_check_route_test.go +++ b/cluster/router/healthcheck/health_check_route_test.go @@ -18,6 +18,7 @@ package healthcheck import ( + "fmt" "math" "testing" "time" @@ -34,13 +35,22 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestHealthCheckRouter_Route(t *testing.T) { +const ( + healthCheckRoute1010IP = "192.168.10.10" + healthCheckRoute1011IP = "192.168.10.11" + healthCheckRoute1012IP = "192.168.10.12" + healthCheckRouteMethodNameTest = "test" + healthCheck1001URL = "dubbo://192.168.10.1/com.ikurento.user.UserProvider" + healthCheckRouteUrlFormat = "dubbo://%s:20000/com.ikurento.user.UserProvider" +) + +func TestHealthCheckRouterRoute(t *testing.T) { defer protocol.CleanAllStatus() - consumerURL, _ := common.NewURL("dubbo://192.168.10.1/com.ikurento.user.UserProvider") + consumerURL, _ := common.NewURL(healthCheck1001URL) 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") + url1, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1010IP)) + url2, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1011IP)) + url3, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1012IP)) hcr, _ := NewHealthCheckRouter(&consumerURL) var invokers []protocol.Invoker @@ -48,21 +58,21 @@ func TestHealthCheckRouter_Route(t *testing.T) { invoker2 := NewMockInvoker(url2) invoker3 := NewMockInvoker(url3) invokers = append(invokers, invoker1, invoker2, invoker3) - inv := invocation.NewRPCInvocation("test", nil, nil) + inv := invocation.NewRPCInvocation(healthCheckRouteMethodNameTest, 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) + request(url1, healthCheckRouteMethodNameTest, 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) + request(url1, healthCheckRouteMethodNameTest, 0, false, false) + request(url2, healthCheckRouteMethodNameTest, 0, false, false) } res = hcr.Route(invokers, &consumerURL, inv) @@ -70,9 +80,9 @@ func TestHealthCheckRouter_Route(t *testing.T) { 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) + request(url1, healthCheckRouteMethodNameTest, 0, false, false) + request(url2, healthCheckRouteMethodNameTest, 0, false, false) + request(url3, healthCheckRouteMethodNameTest, 0, false, false) } res = hcr.Route(invokers, &consumerURL, inv) @@ -80,12 +90,12 @@ func TestHealthCheckRouter_Route(t *testing.T) { assert.True(t, len(res) == 3) // reset the invoker1 successive failed count, so invoker1 go to healthy - request(url1, "test", 0, false, true) + request(url1, healthCheckRouteMethodNameTest, 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) + request(url1, healthCheckRouteMethodNameTest, 0, false, false) } // now all invokers are unhealthy, so downgraded to all again res = hcr.Route(invokers, &consumerURL, inv) @@ -108,7 +118,7 @@ func contains(invokers []protocol.Invoker, invoker protocol.Invoker) bool { func TestNewHealthCheckRouter(t *testing.T) { defer protocol.CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) hcr, _ := NewHealthCheckRouter(&url) h := hcr.(*HealthCheckRouter) assert.Nil(t, h.checker) diff --git a/cluster/router/tag/factory_test.go b/cluster/router/tag/factory_test.go index f11f2944115e0c158b353c7256a850df469b71c5..ee195820c123e1fc67a2c27cd12aaa544650b615 100644 --- a/cluster/router/tag/factory_test.go +++ b/cluster/router/tag/factory_test.go @@ -18,6 +18,7 @@ package tag import ( + "fmt" "testing" ) @@ -29,8 +30,13 @@ import ( "github.com/apache/dubbo-go/common" ) -func TestTagRouterFactory_NewRouter(t *testing.T) { - u1, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true") +const ( + factoryLocalIP = "127.0.0.1" + factoryFormat = "dubbo://%s:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true" +) + +func TestTagRouterFactoryNewRouter(t *testing.T) { + u1, err := common.NewURL(fmt.Sprintf(factoryFormat, factoryLocalIP)) assert.Nil(t, err) factory := NewTagRouterFactory() tagRouter, e := factory.NewPriorityRouter(&u1) diff --git a/cluster/router/tag/file_test.go b/cluster/router/tag/file_test.go index 94fcf9e0e0fabed2445417d14b711f91b65b9e5e..513ba0c0b6c622d6a52fad35a24824121eb71b76 100644 --- a/cluster/router/tag/file_test.go +++ b/cluster/router/tag/file_test.go @@ -29,18 +29,21 @@ import ( "github.com/apache/dubbo-go/common/constant" ) +const ( + fileTestTag = `priority: 100 +force: true` +) + func TestNewFileTagRouter(t *testing.T) { - router, e := NewFileTagRouter([]byte(`priority: 100 -force: true`)) + router, e := NewFileTagRouter([]byte(fileTestTag)) assert.Nil(t, e) assert.NotNil(t, router) assert.Equal(t, 100, router.routerRule.Priority) assert.Equal(t, true, router.routerRule.Force) } -func TestFileTagRouter_URL(t *testing.T) { - router, e := NewFileTagRouter([]byte(`priority: 100 -force: true`)) +func TestFileTagRouterURL(t *testing.T) { + router, e := NewFileTagRouter([]byte(fileTestTag)) assert.Nil(t, e) assert.NotNil(t, router) url := router.URL() @@ -52,9 +55,8 @@ force: true`)) } -func TestFileTagRouter_Priority(t *testing.T) { - router, e := NewFileTagRouter([]byte(`priority: 100 -force: true`)) +func TestFileTagRouterPriority(t *testing.T) { + router, e := NewFileTagRouter([]byte(fileTestTag)) assert.Nil(t, e) assert.NotNil(t, router) priority := router.Priority() diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index 87da418943e90c63a905f35260ada7880d6f51b9..e1376fd96a88246e7ad0e26d2e3c34693c88a77c 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -31,12 +31,14 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// tagRouter defines url, enable and the priority type tagRouter struct { url *common.URL enabled bool priority int64 } +// NewTagRouter returns a tagRouter instance if url is not nil func NewTagRouter(url *common.URL) (*tagRouter, error) { if url == nil { return nil, perrors.Errorf("Illegal route URL!") @@ -48,10 +50,12 @@ func NewTagRouter(url *common.URL) (*tagRouter, error) { }, nil } +// nolint func (c *tagRouter) isEnabled() bool { return c.enabled } +// Route gets a list of invoker func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if !c.isEnabled() { return invokers @@ -62,14 +66,17 @@ func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocati return filterUsingStaticTag(invokers, url, invocation) } +// URL gets the url of tagRouter func (c *tagRouter) URL() common.URL { return *c.url } +// Priority gets the priority of tagRouter func (c *tagRouter) Priority() int64 { return c.priority } +// filterUsingStaticTag gets a list of invoker using static tag func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { if tag, ok := invocation.Attachments()[constant.Tagkey]; ok { result := make([]protocol.Invoker, 0, 8) @@ -86,6 +93,7 @@ func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocati return invokers } +// isForceUseTag returns whether force use tag func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { if b, e := strconv.ParseBool(invocation.AttachmentsByKey(constant.ForceUseTag, url.GetParam(constant.ForceUseTag, "false"))); e == nil { return b diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 280b56c8ccb69eb5d32dae2369bdc862adb8e6fd..000b3ec6724d85590c86456a009d5194c4e71e03 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -32,6 +32,21 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) +const ( + tagRouterTestHangZhouUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou" + tagRouterTestShangHaiUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai" + tagRouterTestBeijingUrl = "dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing" + tagRouterTestUserConsumer = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true" + tagRouterTestUserConsumerTag = "dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true" + + tagRouterTestDubboTag = "dubbo.tag" + tagRouterTestDubboForceTag = "dubbo.force.tag" + tagRouterTestHangZhou = "hangzhou" + tagRouterTestGuangZhou = "guangzhou" + tagRouterTestFalse = "false" + tagRouterTestTrue = "true" +) + // MockInvoker is only mock the Invoker to support test tagRouter type MockInvoker struct { url common.URL @@ -73,8 +88,8 @@ func (bi *MockInvoker) Destroy() { bi.available = false } -func TestTagRouter_Priority(t *testing.T) { - u1, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true") +func TestTagRouterPriority(t *testing.T) { + u1, err := common.NewURL(tagRouterTestUserConsumerTag) assert.Nil(t, err) tagRouter, e := NewTagRouter(&u1) assert.Nil(t, e) @@ -82,15 +97,15 @@ func TestTagRouter_Priority(t *testing.T) { assert.Equal(t, int64(0), p) } -func TestTagRouter_Route_force(t *testing.T) { - u1, e1 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true") +func TestTagRouterRouteForce(t *testing.T) { + u1, e1 := common.NewURL(tagRouterTestUserConsumerTag) assert.Nil(t, e1) tagRouter, e := NewTagRouter(&u1) assert.Nil(t, e) - u2, e2 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou") - u3, e3 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai") - u4, e4 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing") + u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) + u3, e3 := common.NewURL(tagRouterTestShangHaiUrl) + u4, e4 := common.NewURL(tagRouterTestBeijingUrl) assert.Nil(t, e2) assert.Nil(t, e3) assert.Nil(t, e4) @@ -100,29 +115,29 @@ func TestTagRouter_Route_force(t *testing.T) { var invokers []protocol.Invoker invokers = append(invokers, inv2, inv3, inv4) inv := &invocation.RPCInvocation{} - inv.SetAttachments("dubbo.tag", "hangzhou") + inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou) invRst1 := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 1, len(invRst1)) - assert.Equal(t, "hangzhou", invRst1[0].GetUrl().GetParam("dubbo.tag", "")) + assert.Equal(t, tagRouterTestHangZhou, invRst1[0].GetUrl().GetParam(tagRouterTestDubboTag, "")) - inv.SetAttachments("dubbo.tag", "guangzhou") + inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) invRst2 := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 0, len(invRst2)) - inv.SetAttachments("dubbo.force.tag", "false") - inv.SetAttachments("dubbo.tag", "guangzhou") + inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse) + inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) invRst3 := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 3, len(invRst3)) } -func TestTagRouter_Route_noForce(t *testing.T) { - u1, e1 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true") +func TestTagRouterRouteNoForce(t *testing.T) { + u1, e1 := common.NewURL(tagRouterTestUserConsumer) assert.Nil(t, e1) tagRouter, e := NewTagRouter(&u1) assert.Nil(t, e) - u2, e2 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou") - u3, e3 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai") - u4, e4 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing") + u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) + u3, e3 := common.NewURL(tagRouterTestShangHaiUrl) + u4, e4 := common.NewURL(tagRouterTestShangHaiUrl) assert.Nil(t, e2) assert.Nil(t, e3) assert.Nil(t, e4) @@ -132,16 +147,16 @@ func TestTagRouter_Route_noForce(t *testing.T) { var invokers []protocol.Invoker invokers = append(invokers, inv2, inv3, inv4) inv := &invocation.RPCInvocation{} - inv.SetAttachments("dubbo.tag", "hangzhou") + inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou) invRst := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 1, len(invRst)) - assert.Equal(t, "hangzhou", invRst[0].GetUrl().GetParam("dubbo.tag", "")) + assert.Equal(t, tagRouterTestHangZhou, invRst[0].GetUrl().GetParam(tagRouterTestDubboTag, "")) - inv.SetAttachments("dubbo.tag", "guangzhou") - inv.SetAttachments("dubbo.force.tag", "true") + inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) + inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestTrue) invRst1 := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 0, len(invRst1)) - inv.SetAttachments("dubbo.force.tag", "false") + inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse) invRst2 := tagRouter.Route(invokers, &u1, inv) assert.Equal(t, 3, len(invRst2)) } diff --git a/common/constant/default.go b/common/constant/default.go index dd756917cee3f8ee99eb8f9fdb52d9dd2821d307..0680c4192d085237f2e1a460e668b14b26a9b32e 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -77,3 +77,12 @@ const ( const ( COMMA_SPLIT_PATTERN = "\\s*[,]+\\s*" ) + +const ( + SIMPLE_METADATA_SERVICE_NAME = "MetadataService" + DEFAULT_REVIESION = "N/A" +) + +const ( + SERVICE_DISCOVERY_DEFAULT_GROUP = "DEFAULT_GROUP" +) diff --git a/common/constant/env.go b/common/constant/env.go index 5376323328f431083a47395c9e2ebbab5b37f307..f8402b9cb8649f9471d8ae6f0d26314053496bb7 100644 --- a/common/constant/env.go +++ b/common/constant/env.go @@ -17,6 +17,7 @@ package constant +// nolint const ( // CONF_CONSUMER_FILE_PATH ... CONF_CONSUMER_FILE_PATH = "CONF_CONSUMER_FILE_PATH" diff --git a/common/constant/key.go b/common/constant/key.go index 06b37cf709bc4940f6763b72e877d3aab44b2109..c3fe0e79230d3cdf7f41186ac77ca4581d416331 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -43,6 +43,10 @@ const ( DEFAULT_REMOTING_TIMEOUT = 3000 RELEASE_KEY = "release" ANYHOST_KEY = "anyhost" + PORT_KEY = "port" + PROTOCOL_KEY = "protocol" + PATH_SEPARATOR = "/" + DUBBO_KEY = "dubbo" ) const ( @@ -78,6 +82,11 @@ const ( PROVIDER_SHUTDOWN_FILTER = "pshutdown" CONSUMER_SHUTDOWN_FILTER = "cshutdown" SERIALIZATION_KEY = "serialization" + PID_KEY = "pid" + SYNC_REPORT_KEY = "sync.report" + RETRY_PERIOD_KEY = "retry.period" + RETRY_TIMES_KEY = "retry.times" + CYCLE_REPORT_KEY = "cycle.report" ) const ( @@ -151,6 +160,17 @@ const ( NACOS_CATEGORY_KEY = "category" NACOS_PROTOCOL_KEY = "protocol" NACOS_PATH_KEY = "path" + NACOS_NAMESPACE_ID = "namespaceId" + NACOS_PASSWORD = "password" + NACOS_USERNAME = "username" +) + +const ( + ZOOKEEPER_KEY = "zookeeper" +) + +const ( + ETCDV3_KEY = "etcdv3" ) const ( @@ -228,7 +248,6 @@ const ( KEY_SEPARATOR = ":" DEFAULT_PATH_TAG = "metadata" KEY_REVISON_PREFIX = "revision" - PATH_SEPARATOR = "/" // metadata service METADATA_SERVICE_NAME = "org.apache.dubbo.metadata.MetadataService" @@ -257,7 +276,19 @@ const ( ) // service discovery - const ( - NACOS_GROUP = "nacos.group" + SUBSCRIBED_SERVICE_NAMES_KEY = "subscribed-services" + PROVIDER_BY = "provided-by" + EXPORTED_SERVICES_REVISION_PROPERTY_NAME = "dubbo.exported-services.revision" + SUBSCRIBED_SERVICES_REVISION_PROPERTY_NAME = "dubbo.subscribed-services.revision" + SERVICE_INSTANCE_SELECTOR = "service-instance-selector" + METADATA_STORAGE_TYPE_PROPERTY_NAME = "dubbo.metadata.storage-type" + DEFAULT_METADATA_STORAGE_TYPE = "local" + SERVICE_INSTANCE_ENDPOINTS = "dubbo.endpoints" + METADATA_SERVICE_PREFIX = "dubbo.metadata-service." + METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME = METADATA_SERVICE_PREFIX + "url-params" + METADATA_SERVICE_URLS_PROPERTY_NAME = METADATA_SERVICE_PREFIX + "urls" + + // SERVICE_DISCOVERY_KEY indicate which service discovery instance will be used + SERVICE_DISCOVERY_KEY = "service_discovery" ) diff --git a/common/extension/event_dispatcher.go b/common/extension/event_dispatcher.go new file mode 100644 index 0000000000000000000000000000000000000000..f0503e05422844e129a81212beed6af414612b6b --- /dev/null +++ b/common/extension/event_dispatcher.go @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import ( + "sync" +) + +import ( + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/observer" +) + +var ( + globalEventDispatcher observer.EventDispatcher + initEventListeners []func() observer.EventListener + initEventOnce sync.Once +) + +var ( + dispatchers = make(map[string]func() observer.EventDispatcher, 8) +) + +// SetEventDispatcher, actually, it doesn't really init the global dispatcher +func SetEventDispatcher(name string, v func() observer.EventDispatcher) { + dispatchers[name] = v +} + +// SetAndInitGlobalDispatcher will actually init the global dispatcher +// if there is already a global dispatcher, +// it will be override +// if the dispatcher with the name not found, it will panic +func SetAndInitGlobalDispatcher(name string) { + if len(name) == 0 { + name = "direct" + } + if globalEventDispatcher != nil { + logger.Warnf("EventDispatcher has been initialized. It will be replaced") + } + + if dp, ok := dispatchers[name]; !ok || dp == nil { + panic("EventDispatcher for " + name + " is not found, make sure you have import the package, " + + "like import _ github.com/apache/dubbo-go/common/observer/dispatcher ") + } + globalEventDispatcher = dispatchers[name]() +} + +// GetGlobalDispatcher will init all listener and then return dispatcher +func GetGlobalDispatcher() observer.EventDispatcher { + initEventOnce.Do(func() { + // we should delay to add the listeners to avoid some listeners left + for _, l := range initEventListeners { + globalEventDispatcher.AddEventListener(l()) + } + }) + return globalEventDispatcher +} + +// AddEventListener it will be added in global event dispatcher +func AddEventListener(creator func() observer.EventListener) { + initEventListeners = append(initEventListeners, creator) +} diff --git a/common/extension/event_dispatcher_test.go b/common/extension/event_dispatcher_test.go new file mode 100644 index 0000000000000000000000000000000000000000..472360cea5a04c2cd70f0df6ea4db23f6be88f1a --- /dev/null +++ b/common/extension/event_dispatcher_test.go @@ -0,0 +1,111 @@ +/* + * 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 ( + "reflect" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) +import ( + "github.com/apache/dubbo-go/common/observer" +) + +func TestSetAndInitGlobalDispatcher(t *testing.T) { + mock := &mockEventDispatcher{} + SetEventDispatcher("mock", func() observer.EventDispatcher { + return mock + }) + + SetAndInitGlobalDispatcher("mock") + dispatcher := GetGlobalDispatcher() + assert.NotNil(t, dispatcher) + assert.Equal(t, mock, dispatcher) + + mock1 := &mockEventDispatcher{} + + SetEventDispatcher("mock1", func() observer.EventDispatcher { + return mock1 + }) + + SetAndInitGlobalDispatcher("mock1") + dispatcher = GetGlobalDispatcher() + assert.NotNil(t, dispatcher) + assert.Equal(t, mock1, dispatcher) +} + +func TestAddEventListener(t *testing.T) { + AddEventListener(func() observer.EventListener { + return &mockEventListener{} + }) + + AddEventListener(func() observer.EventListener { + return &mockEventListener{} + }) + + assert.Equal(t, 2, len(initEventListeners)) +} + +type mockEventListener struct { +} + +func (m mockEventListener) GetPriority() int { + panic("implement me") +} + +func (m mockEventListener) OnEvent(e observer.Event) error { + panic("implement me") +} + +func (m mockEventListener) GetEventType() reflect.Type { + panic("implement me") +} + +type mockEventDispatcher struct { +} + +func (m mockEventDispatcher) AddEventListener(listener observer.EventListener) { + panic("implement me") +} + +func (m mockEventDispatcher) AddEventListeners(listenersSlice []observer.EventListener) { + panic("implement me") +} + +func (m mockEventDispatcher) RemoveEventListener(listener observer.EventListener) { + panic("implement me") +} + +func (m mockEventDispatcher) RemoveEventListeners(listenersSlice []observer.EventListener) { + panic("implement me") +} + +func (m mockEventDispatcher) GetAllEventListeners() []observer.EventListener { + panic("implement me") +} + +func (m mockEventDispatcher) RemoveAllEventListeners() { + panic("implement me") +} + +func (m mockEventDispatcher) Dispatch(event observer.Event) { + panic("implement me") +} diff --git a/common/extension/metadata_report_factory.go b/common/extension/metadata_report_factory.go index c55f8617fadd9d09d68547b05341d127716ce73c..593318d01beec1f89d8194c6d4bd18a15c798a0e 100644 --- a/common/extension/metadata_report_factory.go +++ b/common/extension/metadata_report_factory.go @@ -18,20 +18,20 @@ package extension import ( - "github.com/apache/dubbo-go/metadata" + "github.com/apache/dubbo-go/metadata/report/factory" ) var ( - metaDataReportFactories = make(map[string]func() metadata.MetadataReportFactory, 8) + metaDataReportFactories = make(map[string]func() factory.MetadataReportFactory, 8) ) // SetMetadataReportFactory sets the MetadataReportFactory with @name -func SetMetadataReportFactory(name string, v func() metadata.MetadataReportFactory) { +func SetMetadataReportFactory(name string, v func() factory.MetadataReportFactory) { metaDataReportFactories[name] = v } // GetMetadataReportFactory finds the MetadataReportFactory with @name -func GetMetadataReportFactory(name string) metadata.MetadataReportFactory { +func GetMetadataReportFactory(name string) factory.MetadataReportFactory { if metaDataReportFactories[name] == nil { panic("metadata report for " + name + " is not existing, make sure you have import the package.") } diff --git a/common/extension/metadata_service.go b/common/extension/metadata_service.go new file mode 100644 index 0000000000000000000000000000000000000000..1823273b8f7f86b2d96abf990359e14b569abddb --- /dev/null +++ b/common/extension/metadata_service.go @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import ( + "fmt" +) + +import ( + "github.com/apache/dubbo-go/metadata/service" +) + +var ( + // there will be two types: local or remote + metadataServiceInsMap = make(map[string]func() (service.MetadataService, error), 2) +) + +// SetMetadataService will store the msType => creator pair +func SetMetadataService(msType string, creator func() (service.MetadataService, error)) { + metadataServiceInsMap[msType] = creator +} + +// GetMetadataService will create a MetadataService instance +// it will panic if msType not found +func GetMetadataService(msType string) (service.MetadataService, error) { + if creator, ok := metadataServiceInsMap[msType]; ok { + return creator() + } + panic(fmt.Sprintf("could not find the metadata service creator for metadataType: %s, please check whether you have imported relative packages, \n"+ + "local - github.com/apache/dubbo-go/metadata/service/inmemory, \n"+ + "remote - github.com/apache/dubbo-go/metadata/service/remote", msType)) +} diff --git a/common/extension/metadata_service_proxy_factory.go b/common/extension/metadata_service_proxy_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..2b88d37c9a5145ddca81930cd76ccac1a7184318 --- /dev/null +++ b/common/extension/metadata_service_proxy_factory.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 extension + +import ( + "fmt" +) + +import ( + "github.com/apache/dubbo-go/metadata/service" +) + +var ( + metadataServiceProxyFactoryMap = make(map[string]func() service.MetadataServiceProxyFactory, 2) +) + +type MetadataServiceProxyFactoryFunc func() service.MetadataServiceProxyFactory + +// SetMetadataServiceProxyFactory store the name-creator pair +func SetMetadataServiceProxyFactory(name string, creator MetadataServiceProxyFactoryFunc) { + metadataServiceProxyFactoryMap[name] = creator +} + +// GetMetadataServiceProxyFactory will create an instance. +// it will panic if the factory with name not found +func GetMetadataServiceProxyFactory(name string) service.MetadataServiceProxyFactory { + if f, ok := metadataServiceProxyFactoryMap[name]; ok { + return f() + } + panic(fmt.Sprintf("could not find the metadata service factory creator for name: %s, please check whether you have imported relative packages, \n"+ + "local - github.com/apache/dubbo-go/metadata/service/inmemory, \n"+ + "remote - github.com/apache/dubbo-go/metadata/service/remote", name)) +} diff --git a/common/extension/service_discovery.go b/common/extension/service_discovery.go index 1d891c2189e55fbd6fa03e4c6e5ea6ec5b654e46..0227920dc64a7b5f6ad1939fcccbb7384c43f68d 100644 --- a/common/extension/service_discovery.go +++ b/common/extension/service_discovery.go @@ -21,25 +21,28 @@ import ( perrors "github.com/pkg/errors" ) import ( - "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/registry" ) var ( - discoveryCreatorMap = make(map[string]func(url *common.URL) (registry.ServiceDiscovery, error), 4) + discoveryCreatorMap = make(map[string]func(name string) (registry.ServiceDiscovery, error), 4) ) // SetServiceDiscovery will store the @creator and @name -func SetServiceDiscovery(name string, creator func(_ *common.URL) (registry.ServiceDiscovery, error)) { - discoveryCreatorMap[name] = creator +// protocol indicate the implementation, like nacos +// the name like nacos-1... +func SetServiceDiscovery(protocol string, creator func(name string) (registry.ServiceDiscovery, error)) { + discoveryCreatorMap[protocol] = creator } // GetServiceDiscovery will return the registry.ServiceDiscovery +// protocol indicate the implementation, like nacos +// the name like nacos-1... // if not found, or initialize instance failed, it will return error. -func GetServiceDiscovery(name string, url *common.URL) (registry.ServiceDiscovery, error) { - creator, ok := discoveryCreatorMap[name] +func GetServiceDiscovery(protocol string, name string) (registry.ServiceDiscovery, error) { + creator, ok := discoveryCreatorMap[protocol] if !ok { - return nil, perrors.New("Could not find the service discovery with name: " + name) + return nil, perrors.New("Could not find the service discovery with discovery protocol: " + protocol) } - return creator(url) + return creator(name) } diff --git a/common/extension/service_instance_customizer.go b/common/extension/service_instance_customizer.go new file mode 100644 index 0000000000000000000000000000000000000000..3ebb3e40f5851ad4799e7cee5966edd77cea2f9e --- /dev/null +++ b/common/extension/service_instance_customizer.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 extension + +import ( + "sort" +) + +import ( + "github.com/apache/dubbo-go/registry" +) + +var ( + customizers = make([]registry.ServiceInstanceCustomizer, 0, 8) +) + +// AddCustomizers will put the customizer into slices and then sort them; +// this method will be invoked several time, so we sort them here. +func AddCustomizers(cus registry.ServiceInstanceCustomizer) { + customizers = append(customizers, cus) + sort.Stable(customizerSlice(customizers)) +} + +// GetCustomizers will return the sorted customizer +// the result won't be nil +func GetCustomizers() []registry.ServiceInstanceCustomizer { + return customizers +} + +type customizerSlice []registry.ServiceInstanceCustomizer + +// nolint +func (c customizerSlice) Len() int { + return len(c) +} + +// nolint +func (c customizerSlice) Swap(i, j int) { c[i], c[j] = c[j], c[i] } + +// nolint +func (c customizerSlice) Less(i, j int) bool { return c[i].GetPriority() < c[j].GetPriority() } diff --git a/common/extension/service_instance_selector_factory.go b/common/extension/service_instance_selector_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..66d3e7646e6c26639901fe0a9c8994233aa7c567 --- /dev/null +++ b/common/extension/service_instance_selector_factory.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 extension + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/registry/servicediscovery/instance" +) + +var ( + serviceInstanceSelectorMappings = make(map[string]func() instance.ServiceInstanceSelector, 2) +) + +// nolint +func SetServiceInstanceSelector(name string, f func() instance.ServiceInstanceSelector) { + serviceInstanceSelectorMappings[name] = f +} + +// GetServiceInstanceSelector will create an instance +// it will panic if selector with the @name not found +func GetServiceInstanceSelector(name string) (instance.ServiceInstanceSelector, error) { + serviceInstanceSelector, ok := serviceInstanceSelectorMappings[name] + if !ok { + return nil, perrors.New("Could not find service instance selector with" + + "name:" + name) + } + return serviceInstanceSelector(), nil +} diff --git a/common/extension/service_name_mapping.go b/common/extension/service_name_mapping.go new file mode 100644 index 0000000000000000000000000000000000000000..99fd4c25e93d6f3d085a8bcab50482535e8c9019 --- /dev/null +++ b/common/extension/service_name_mapping.go @@ -0,0 +1,36 @@ +/* + * 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/metadata/mapping" +) + +type ServiceNameMappingCreator func() mapping.ServiceNameMapping + +var ( + globalNameMappingCreator ServiceNameMappingCreator +) + +func SetGlobalServiceNameMapping(nameMappingCreator ServiceNameMappingCreator) { + globalNameMappingCreator = nameMappingCreator +} + +func GetGlobalServiceNameMapping() mapping.ServiceNameMapping { + return globalNameMappingCreator() +} diff --git a/common/observer/dispatcher/direct_event_dispatcher.go b/common/observer/dispatcher/direct_event_dispatcher.go new file mode 100644 index 0000000000000000000000000000000000000000..a2d334ce2ca9461a8e14e28bb0f3ecf21971751f --- /dev/null +++ b/common/observer/dispatcher/direct_event_dispatcher.go @@ -0,0 +1,69 @@ +/* + * 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 dispatcher + +import ( + "reflect" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/observer" +) + +func init() { + extension.SetEventDispatcher("direct", NewDirectEventDispatcher) +} + +// DirectEventDispatcher is align with DirectEventDispatcher interface in Java. +// it's the top abstraction +// Align with 2.7.5 +// Dispatcher event to listener direct +type DirectEventDispatcher struct { + observer.BaseListener +} + +// NewDirectEventDispatcher ac constructor of DirectEventDispatcher +func NewDirectEventDispatcher() observer.EventDispatcher { + return &DirectEventDispatcher{ + BaseListener: observer.NewBaseListener(), + } +} + +// Dispatch event directly +// it lookup the listener by event's type. +// if listener not found, it just return and do nothing +func (ded *DirectEventDispatcher) Dispatch(event observer.Event) { + if event == nil { + logger.Warnf("[DirectEventDispatcher] dispatch event nil") + return + } + eventType := reflect.TypeOf(event).Elem() + ded.Mutex.RLock() + defer ded.Mutex.RUnlock() + listenersSlice, loaded := ded.ListenersCache[eventType] + if !loaded { + return + } + for _, listener := range listenersSlice { + if err := listener.OnEvent(event); err != nil { + logger.Warnf("[DirectEventDispatcher] dispatch event error:%v", err) + } + } +} diff --git a/common/observer/dispatcher/direct_event_dispatcher_test.go b/common/observer/dispatcher/direct_event_dispatcher_test.go new file mode 100644 index 0000000000000000000000000000000000000000..12facbb9c4bd1b33fa071a50beae87b77e1be968 --- /dev/null +++ b/common/observer/dispatcher/direct_event_dispatcher_test.go @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dispatcher + +import ( + "fmt" + "reflect" + "testing" +) + +import ( + "github.com/apache/dubbo-go/common/observer" +) + +func TestDirectEventDispatcher_Dispatch(t *testing.T) { + ded := NewDirectEventDispatcher() + ded.AddEventListener(&TestEventListener{ + BaseListener: observer.NewBaseListener(), + }) + ded.AddEventListener(&TestEventListener1{}) + ded.Dispatch(&TestEvent{}) + ded.Dispatch(nil) +} + +type TestEvent struct { + observer.BaseEvent +} + +type TestEventListener struct { + observer.BaseListener + observer.EventListener +} + +func (tel *TestEventListener) OnEvent(e observer.Event) error { + fmt.Println("TestEventListener") + return nil +} + +func (tel *TestEventListener) GetPriority() int { + return -1 +} + +func (tel *TestEventListener) GetEventType() reflect.Type { + return reflect.TypeOf(&TestEvent{}) +} + +type TestEventListener1 struct { + observer.EventListener +} + +func (tel *TestEventListener1) OnEvent(e observer.Event) error { + fmt.Println("TestEventListener1") + return nil +} + +func (tel *TestEventListener1) GetPriority() int { + return 1 +} + +func (tel *TestEventListener1) GetEventType() reflect.Type { + return reflect.TypeOf(TestEvent{}) +} diff --git a/common/observer/dispatcher/mock_event_dispatcher.go b/common/observer/dispatcher/mock_event_dispatcher.go new file mode 100644 index 0000000000000000000000000000000000000000..45cdaa71a257ff78293e1a1bdc15232c36a9aa26 --- /dev/null +++ b/common/observer/dispatcher/mock_event_dispatcher.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 dispatcher + +import ( + "github.com/apache/dubbo-go/common/observer" +) + +// MockEventDispatcher will do nothing. +// It is only used by tests +// Now the implementation doing nothing, +// But you can modify this if needed +type MockEventDispatcher struct { +} + +// AddEventListener do nothing +func (m MockEventDispatcher) AddEventListener(listener observer.EventListener) { +} + +// AddEventListeners do nothing +func (m MockEventDispatcher) AddEventListeners(listenersSlice []observer.EventListener) { +} + +// RemoveEventListener do nothing +func (m MockEventDispatcher) RemoveEventListener(listener observer.EventListener) { +} + +// RemoveEventListeners do nothing +func (m MockEventDispatcher) RemoveEventListeners(listenersSlice []observer.EventListener) { +} + +// GetAllEventListeners return empty list +func (m MockEventDispatcher) GetAllEventListeners() []observer.EventListener { + return make([]observer.EventListener, 0) +} + +// RemoveAllEventListeners do nothing +func (m MockEventDispatcher) RemoveAllEventListeners() { +} + +// Dispatch do nothing +func (m MockEventDispatcher) Dispatch(event observer.Event) { +} diff --git a/common/observer/event.go b/common/observer/event.go new file mode 100644 index 0000000000000000000000000000000000000000..209a50c78a36c13d789f6f9fbc81a73a5d9a535f --- /dev/null +++ b/common/observer/event.go @@ -0,0 +1,68 @@ +/* + * 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 observer + +import ( + "fmt" + "math/rand" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// Event is align with Event interface in Java. +// it's the top abstraction +// Align with 2.7.5 +type Event interface { + fmt.Stringer + GetSource() interface{} + GetTimestamp() time.Time +} + +// BaseEvent is the base implementation of Event +// You should never use it directly +type BaseEvent struct { + Source interface{} + Timestamp time.Time +} + +// GetSource return the source +func (b *BaseEvent) GetSource() interface{} { + return b.Source +} + +// GetTimestamp return the Timestamp when the event is created +func (b *BaseEvent) GetTimestamp() time.Time { + return b.Timestamp +} + +// String return a human readable string representing this event +func (b *BaseEvent) String() string { + return fmt.Sprintf("BaseEvent[source = %#v]", b.Source) +} + +// NewBaseEvent create an BaseEvent instance +// and the Timestamp will be current timestamp +func NewBaseEvent(source interface{}) *BaseEvent { + return &BaseEvent{ + Source: source, + Timestamp: time.Now(), + } +} diff --git a/common/observer/event_dispatcher.go b/common/observer/event_dispatcher.go new file mode 100644 index 0000000000000000000000000000000000000000..17745e68c07065f954d456914fb276fadf6d975d --- /dev/null +++ b/common/observer/event_dispatcher.go @@ -0,0 +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 observer + +// EventDispatcher is align with EventDispatcher interface in Java. +// it's the top abstraction +// Align with 2.7.5 +type EventDispatcher interface { + Listenable + // Dispatch event + Dispatch(event Event) +} diff --git a/common/observer/event_listener.go b/common/observer/event_listener.go new file mode 100644 index 0000000000000000000000000000000000000000..faa8705a4292b1441b9605852017256a9305f624 --- /dev/null +++ b/common/observer/event_listener.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 observer + +import ( + "reflect" +) + +import ( + gxsort "github.com/dubbogo/gost/sort" +) + +// EventListener is an new interface used to align with dubbo 2.7.5 +// It contains the Prioritized means that the listener has its priority +// Usually the priority of your custom implementation should be between [100, 9000] +// the number outside the range will be though as system reserve number +// usually implementation should be singleton +type EventListener interface { + gxsort.Prioritizer + // OnEvent handle this event + OnEvent(e Event) error + // GetEventType listen which event type + GetEventType() reflect.Type +} + +// ConditionalEventListener only handle the event which it can handle +type ConditionalEventListener interface { + EventListener + // Accept will make the decision whether it should handle this event + Accept(e Event) bool +} + +type ChangedNotify interface { + Notify(e Event) +} diff --git a/common/observer/listenable.go b/common/observer/listenable.go new file mode 100644 index 0000000000000000000000000000000000000000..bf6ae7219bfd3bd981cf5cd2c87a57c57d7df5d8 --- /dev/null +++ b/common/observer/listenable.go @@ -0,0 +1,142 @@ +/* + * 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 observer + +import ( + "reflect" + "sort" + "sync" +) + +// Listenable could add and remove the event listener +type Listenable interface { + AddEventListener(listener EventListener) + AddEventListeners(listenersSlice []EventListener) + RemoveEventListener(listener EventListener) + RemoveEventListeners(listenersSlice []EventListener) + GetAllEventListeners() []EventListener + RemoveAllEventListeners() +} + +// BaseListener base listenable +type BaseListener struct { + Listenable + ListenersCache map[reflect.Type][]EventListener + Mutex sync.RWMutex +} + +// NewBaseListener a constructor of base listenable +func NewBaseListener() BaseListener { + return BaseListener{ + ListenersCache: make(map[reflect.Type][]EventListener, 8), + } +} + +// AddEventListener add event listener +func (bl *BaseListener) AddEventListener(listener EventListener) { + eventType := listener.GetEventType() + if eventType.Kind() == reflect.Ptr { + eventType = eventType.Elem() + } + bl.Mutex.Lock() + defer bl.Mutex.Unlock() + listenersSlice, loaded := bl.ListenersCache[eventType] + if !loaded { + listenersSlice = make([]EventListener, 0, 8) + } + // return if listenersSlice already has this listener + if loaded && containListener(listenersSlice, listener) { + return + } + listenersSlice = append(listenersSlice, listener) + sort.Slice(listenersSlice, func(i, j int) bool { + return listenersSlice[i].GetPriority() < listenersSlice[j].GetPriority() + }) + bl.ListenersCache[eventType] = listenersSlice +} + +// AddEventListeners add the slice of event listener +func (bl *BaseListener) AddEventListeners(listenersSlice []EventListener) { + for _, listener := range listenersSlice { + bl.AddEventListener(listener) + } +} + +// RemoveEventListener remove the event listener +func (bl *BaseListener) RemoveEventListener(listener EventListener) { + eventType := listener.GetEventType() + if eventType.Kind() == reflect.Ptr { + eventType = eventType.Elem() + } + bl.Mutex.Lock() + defer bl.Mutex.Unlock() + listenersSlice, loaded := bl.ListenersCache[eventType] + if !loaded { + return + } + for i, l := range listenersSlice { + if l == listener { + listenersSlice = append(listenersSlice[:i], listenersSlice[i+1:]...) + } + } + bl.ListenersCache[eventType] = listenersSlice +} + +// RemoveEventListeners remove the slice of event listener +// it will iterate all listener and remove it one by one +func (bl *BaseListener) RemoveEventListeners(listenersSlice []EventListener) { + for _, listener := range listenersSlice { + bl.RemoveEventListener(listener) + } +} + +// RemoveAllEventListeners remove all +// using Lock +func (bl *BaseListener) RemoveAllEventListeners() { + bl.Mutex.Lock() + defer bl.Mutex.Unlock() + bl.ListenersCache = make(map[reflect.Type][]EventListener, 8) +} + +// GetAllEventListeners get all listener +// using RLock +func (bl *BaseListener) GetAllEventListeners() []EventListener { + allListenersSlice := make([]EventListener, 0, 16) + + bl.Mutex.RLock() + defer bl.Mutex.RUnlock() + for _, listenersSlice := range bl.ListenersCache { + allListenersSlice = append(allListenersSlice, listenersSlice...) + } + sort.Slice(allListenersSlice, func(i, j int) bool { + return allListenersSlice[i].GetPriority() < allListenersSlice[j].GetPriority() + }) + return allListenersSlice +} + +// containListener true if contain listener +// it's not thread safe +// usually it should be use in lock scope +func containListener(listenersSlice []EventListener, listener EventListener) bool { + for _, loadListener := range listenersSlice { + if loadListener == listener { + return true + } + } + return false +} diff --git a/common/observer/listenable_test.go b/common/observer/listenable_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5a03382a937fe925b6e17b495d066b86c8d2161d --- /dev/null +++ b/common/observer/listenable_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 observer + +import ( + "reflect" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestListenable(t *testing.T) { + el := &TestEventListener{} + bl := NewBaseListener() + b := &bl + b.AddEventListener(el) + b.AddEventListener(el) + al := b.GetAllEventListeners() + assert.Equal(t, len(al), 1) + assert.Equal(t, al[0].GetEventType(), reflect.TypeOf(TestEvent{})) + b.RemoveEventListener(el) + assert.Equal(t, len(b.GetAllEventListeners()), 0) + var ts []EventListener + ts = append(ts, el) + b.AddEventListeners(ts) + assert.Equal(t, len(al), 1) + +} + +type TestEvent struct { + BaseEvent +} + +type TestEventListener struct { + EventListener +} + +func (tel *TestEventListener) OnEvent(e Event) error { + return nil +} + +func (tel *TestEventListener) GetPriority() int { + return -1 +} + +func (tel *TestEventListener) GetEventType() reflect.Type { + return reflect.TypeOf(TestEvent{}) +} diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index abcf87cd9d297769bf8aff6fa07d6a4659091eb6..ce0f4d1d3f4dc8b93467aaeede40ea03b53c6e66 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -23,6 +23,11 @@ import ( "sync" ) +import ( + "github.com/apache/dubbo-go-hessian2/java_exception" + perrors "github.com/pkg/errors" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" @@ -154,7 +159,18 @@ func (p *Proxy) Implement(v common.RPCService) { } err = result.Error() - logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) + if err != nil { + // the cause reason + err = perrors.Cause(err) + // if some error happened, it should be log some info in the seperate file. + if throwabler, ok := err.(java_exception.Throwabler); ok { + logger.Warnf("invoke service throw exception: %v , stackTraceElements: %v", err.Error(), throwabler.GetStackTrace()) + } else { + logger.Warnf("result err: %v", err) + } + } else { + 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/rpc_service.go b/common/rpc_service.go index 05ca3721d98124ccbdebedb47b5ae4edd8b84add..9ef2b956aa955f4fc79c6f75bd060ccfee2d02ca 100644 --- a/common/rpc_service.go +++ b/common/rpc_service.go @@ -131,6 +131,11 @@ func (s *Service) Method() map[string]*MethodType { return s.methods } +// Name will return service name +func (s *Service) Name() string { + return s.name +} + // RcvrType gets @s.rcvrType. func (s *Service) RcvrType() reflect.Type { return s.rcvrType @@ -272,7 +277,7 @@ func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) erro } } delete(svcs, serviceId) - if len(sm.serviceMap) == 0 { + if len(sm.serviceMap[protocol]) == 0 { delete(sm.serviceMap, protocol) } @@ -337,6 +342,16 @@ func suiteMethod(method reflect.Method) *MethodType { argsType []reflect.Type ) + // this method is in RPCService + // we force users must implement RPCService interface in their provider + // and RPCService has only one method "Reference" + // In general, this method should not be exported to client + // so we ignore this method + // see RPCService + if mname == "Reference" { + return nil + } + if outNum != 1 && outNum != 2 { logger.Warnf("method %s of mtype %v has wrong number of in out parameters %d; needs exactly 1/2", mname, mtype.String(), outNum) diff --git a/common/url.go b/common/url.go index 5a3e69f266ab509881b6fdafb21926732f68aa94..7c2c39c60feccccdc428e1e427a6261c743bd92b 100644 --- a/common/url.go +++ b/common/url.go @@ -38,6 +38,7 @@ import ( import ( "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" ) // /////////////////////////////// @@ -180,7 +181,12 @@ func WithToken(token string) option { if len(token) > 0 { value := token if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" { - value = uuid.NewV4().String() + u, err := uuid.NewV4() + if err != nil { + logger.Errorf("could not generator UUID: %v", err) + return + } + value = u.String() } url.SetParam(constant.TOKEN_KEY, value) } @@ -681,3 +687,22 @@ func ParamsUnescapeEncode(params url.Values) string { } return buf.String() } + +// URLSlice will be used to sort URL instance +// Instances will be order by URL.String() +type URLSlice []URL + +// nolint +func (s URLSlice) Len() int { + return len(s) +} + +// nolint +func (s URLSlice) Less(i, j int) bool { + return s[i].String() < s[j].String() +} + +// nolint +func (s URLSlice) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} diff --git a/config/application_config.go b/config/application_config.go index cc02da3debd77b1693e26ee8b522330f3c2f23c9..2b9b91c3b34167321e6f30e2427d20e71ecbf76b 100644 --- a/config/application_config.go +++ b/config/application_config.go @@ -33,7 +33,8 @@ type ApplicationConfig struct { Version string `yaml:"version" json:"version,omitempty" property:"version"` Owner string `yaml:"owner" json:"owner,omitempty" property:"owner"` Environment string `yaml:"environment" json:"environment,omitempty" property:"environment"` - MetadataType string `default:"local" yaml:"metadataType" json:"metadataType,omitempty" property:"metadataType"` //field for metadata report + // the metadata type. remote or local + MetadataType string `default:"local" yaml:"metadataType" json:"metadataType,omitempty" property:"metadataType"` } // nolint diff --git a/config/base_config.go b/config/base_config.go index cf490019b2a48b98ffb6fab1036bfd6218fe7343..0ba5bc7ef98cb30a13890b93a659c467adcbf73b 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -43,13 +43,33 @@ type multiConfiger interface { // BaseConfig is the common configuration for provider and consumer type BaseConfig struct { ConfigCenterConfig *ConfigCenterConfig `yaml:"config_center" json:"config_center,omitempty"` - configCenterUrl *common.URL - prefix string - fatherConfig interface{} - MetricConfig *MetricConfig `yaml:"metrics" json:"metrics,omitempty"` + // since 1.5.0 version + Remotes map[string]*RemoteConfig `yaml:"remote" json:"remote,omitempty"` + ServiceDiscoveries map[string]*ServiceDiscoveryConfig `yaml:"service_discovery" json:"service_discovery,omitempty"` + MetadataReportConfig *MetadataReportConfig `yaml:"metadata_report" json:"metadata_report,omitempty" property:"metadata_report"` - fileStream *bytes.Buffer + // application config + ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"` + + configCenterUrl *common.URL + prefix string + fatherConfig interface{} + EventDispatcherType string `default:"direct" yaml:"event_dispatcher_type" json:"event_dispatcher_type,omitempty"` + MetricConfig *MetricConfig `yaml:"metrics" json:"metrics,omitempty"` + fileStream *bytes.Buffer +} + +// nolint +func (c *BaseConfig) GetServiceDiscoveries(name string) (config *ServiceDiscoveryConfig, ok bool) { + config, ok = c.ServiceDiscoveries[name] + return +} + +// GetRemoteConfig will return the remote's config with the name if found +func (c *BaseConfig) GetRemoteConfig(name string) (config *RemoteConfig, ok bool) { + config, ok = c.Remotes[name] + return } // startConfigCenter will start the config center. @@ -64,7 +84,7 @@ func (c *BaseConfig) startConfigCenter() error { if c.prepareEnvironment() != nil { return perrors.WithMessagef(err, "start config center error!") } - //c.fresh() + // c.fresh() return err } @@ -101,14 +121,14 @@ func (c *BaseConfig) prepareEnvironment() error { return perrors.WithStack(err) } } - //global config file + // global config file mapContent, err := dynamicConfig.Parser().Parse(content) if err != nil { return perrors.WithStack(err) } config.GetEnvInstance().UpdateExternalConfigMap(mapContent) - //appGroup config file + // appGroup config file if len(appContent) != 0 { appMapConent, err := dynamicConfig.Parser().Parse(appContent) if err != nil { @@ -124,20 +144,20 @@ func getKeyPrefix(val reflect.Value) []string { var ( prefix string ) - + configPrefixMethod := "Prefix" if val.CanAddr() { - prefix = val.Addr().MethodByName("Prefix").Call(nil)[0].String() + prefix = val.Addr().MethodByName(configPrefixMethod).Call(nil)[0].String() } else { - prefix = val.MethodByName("Prefix").Call(nil)[0].String() + prefix = val.MethodByName(configPrefixMethod).Call(nil)[0].String() } - var retPrefixs []string + var retPrefixes []string for _, pfx := range strings.Split(prefix, "|") { - retPrefixs = append(retPrefixs, pfx) + retPrefixes = append(retPrefixes, pfx) } - return retPrefixs + return retPrefixes } @@ -164,13 +184,13 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC idStr string ) - prefixs := getKeyPrefix(val) + prefixes := getKeyPrefix(val) if id.Kind() == reflect.String { idStr = id.Interface().(string) } - for _, pfx := range prefixs { + for _, pfx := range prefixes { if len(pfx) > 0 { if len(idStr) > 0 { @@ -190,18 +210,20 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC } if ok { + errMsg := func(structName string, fieldName string, errorDetails error) { + logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", + structName, fieldName, errorDetails) + } switch f.Kind() { case reflect.Int64: x, err := strconv.Atoi(value) if err != nil { - logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", - val.Type().Name(), val.Type().Field(i).Name, err) + errMsg(val.Type().Name(), val.Type().Field(i).Name, err) } else { if !f.OverflowInt(int64(x)) { f.SetInt(int64(x)) } else { - logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", - val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the int64 value {%v} from config center is overflow", int64(x))) + errMsg(val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the int64 value {%v} from config center is overflow", int64(x))) } } case reflect.String: @@ -209,21 +231,18 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC case reflect.Bool: x, err := strconv.ParseBool(value) if err != nil { - logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", - val.Type().Name(), val.Type().Field(i).Name, err) + errMsg(val.Type().Name(), val.Type().Field(i).Name, err) } f.SetBool(x) case reflect.Float64: x, err := strconv.ParseFloat(value, 64) if err != nil { - logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", - val.Type().Name(), val.Type().Field(i).Name, err) + errMsg(val.Type().Name(), val.Type().Field(i).Name, err) } else { if !f.OverflowFloat(x) { f.SetFloat(x) } else { - logger.Errorf("Dynamic change the configuration in struct {%v} field {%v} error ,error message is {%v}", - val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the float64 value {%v} from config center is overflow", x)) + errMsg(val.Type().Name(), val.Type().Field(i).Name, perrors.Errorf("the float64 value {%v} from config center is overflow", x)) } } default: @@ -264,7 +283,7 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC if f.Kind() == reflect.Map { if f.Type().Elem().Kind() == reflect.Ptr { - //initiate config + // initiate config s := reflect.New(f.Type().Elem().Elem()) prefix := s.MethodByName("Prefix").Call(nil)[0].String() for _, pfx := range strings.Split(prefix, "|") { @@ -279,7 +298,7 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC } - //iter := f.MapRange() + // iter := f.MapRange() for _, k := range f.MapKeys() { v := f.MapIndex(k) @@ -314,7 +333,7 @@ func (c *BaseConfig) fresh() { } func (c *BaseConfig) freshInternalConfig(config *config.InmemoryConfiguration) { - //reflect to init struct + // reflect to init struct tp := reflect.ValueOf(c.fatherConfig).Elem().Type() initializeStruct(tp, reflect.ValueOf(c.fatherConfig).Elem()) @@ -328,39 +347,40 @@ func (c *BaseConfig) SetFatherConfig(fatherConfig interface{}) { } func initializeStruct(t reflect.Type, v reflect.Value) { - if v.Kind() == reflect.Struct { - for i := 0; i < v.NumField(); i++ { - f := v.Field(i) - ft := t.Field(i) - - if ft.Tag.Get("property") != "" { - switch ft.Type.Kind() { - case reflect.Map: - if f.IsNil() { - f.Set(reflect.MakeMap(ft.Type)) - } - case reflect.Slice: - if f.IsNil() { - f.Set(reflect.MakeSlice(ft.Type, 0, 0)) - } - case reflect.Chan: - if f.IsNil() { - f.Set(reflect.MakeChan(ft.Type, 0)) - } - case reflect.Struct: - if f.IsNil() { - initializeStruct(ft.Type, f) - } - case reflect.Ptr: - if f.IsNil() { - fv := reflect.New(ft.Type.Elem()) - initializeStruct(ft.Type.Elem(), fv.Elem()) - f.Set(fv) - } - default: - } - } + if v.Kind() != reflect.Struct { + return + } + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + ft := t.Field(i) + if ft.Tag.Get("property") == "" { + continue + } + switch ft.Type.Kind() { + case reflect.Map: + if f.IsNil() { + f.Set(reflect.MakeMap(ft.Type)) + } + case reflect.Slice: + if f.IsNil() { + f.Set(reflect.MakeSlice(ft.Type, 0, 0)) + } + case reflect.Chan: + if f.IsNil() { + f.Set(reflect.MakeChan(ft.Type, 0)) + } + case reflect.Struct: + if f.IsNil() { + initializeStruct(ft.Type, f) + } + case reflect.Ptr: + if f.IsNil() { + fv := reflect.New(ft.Type.Elem()) + initializeStruct(ft.Type.Elem(), fv.Elem()) + f.Set(fv) + } + default: } } } diff --git a/config/base_config_test.go b/config/base_config_test.go index bc422d018946017a2c50dccefe54357d786a7589..6db6a8dcb84de3fdefe94cce87338b9efe28246c 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -33,89 +33,95 @@ import ( _ "github.com/apache/dubbo-go/config_center/apollo" ) -func Test_refresh(t *testing.T) { +func getMockMap() map[string]string { + baseMockMap := map[string]string{ + "dubbo.registries.shanghai_reg1.protocol": "mock100", + "dubbo.reference.com.MockService.MockService.retries": "10", + "dubbo.com.MockService.MockService.GetUser.retries": "10", + "dubbo.consumer.check": "false", + "dubbo.application.name": "dubbo", + } + return baseMockMap +} + +var baseAppConfig = &ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "2.6.0", + Owner: "dubbo", + Environment: "test", +} + +var baseRegistries = map[string]*RegistryConfig{ + "shanghai_reg2": { + Protocol: "mock", + TimeoutStr: "2s", + Group: "shanghai_idc", + Address: "127.0.0.2:2181", + Username: "user1", + Password: "pwd1", + }, + "hangzhou_reg1": { + Protocol: "mock", + TimeoutStr: "2s", + Group: "hangzhou_idc", + Address: "127.0.0.3:2181", + Username: "user1", + Password: "pwd1", + }, + "hangzhou_reg2": { + Protocol: "mock", + TimeoutStr: "2s", + Group: "hangzhou_idc", + Address: "127.0.0.4:2181", + Username: "user1", + Password: "pwd1", + }, +} + +var baseMockRef = map[string]*ReferenceConfig{ + "MockService": { + InterfaceName: "com.MockService", + Protocol: "mock", + Cluster: "failover", + Loadbalance: "random", + Retries: "3", + Group: "huadong_idc", + Version: "1.0.0", + Methods: []*MethodConfig{ + { + InterfaceId: "MockService", + InterfaceName: "com.MockService", + Name: "GetUser", + Retries: "2", + LoadBalance: "random", + }, + { + InterfaceId: "MockService", + InterfaceName: "com.MockService", + Name: "GetUser1", + Retries: "2", + LoadBalance: "random", + }, + }, + }, +} + +func TestRefresh(t *testing.T) { c := &BaseConfig{} - mockMap := map[string]string{} - mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100" - mockMap["dubbo.reference.com.MockService.MockService.retries"] = "10" - mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10" - mockMap["dubbo.consumer.check"] = "false" - mockMap["dubbo.application.name"] = "dubbo" + mockMap := getMockMap() mockMap["dubbo.shutdown.timeout"] = "12s" config.GetEnvInstance().UpdateExternalConfigMap(mockMap) father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, - Registries: map[string]*RegistryConfig{ - //"shanghai_reg1": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, - "shanghai_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "shanghai_idc", - Address: "127.0.0.2:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg1": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.3:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.4:2181", - Username: "user1", - Password: "pwd1", - }, - }, - References: map[string]*ReferenceConfig{ - "MockService": { - InterfaceName: "com.MockService", - Protocol: "mock", - Cluster: "failover", - Loadbalance: "random", - Retries: "3", - Group: "huadong_idc", - Version: "1.0.0", - Methods: []*MethodConfig{ - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser", - Retries: "2", - Loadbalance: "random", - }, - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser1", - Retries: "2", - Loadbalance: "random", - }, - }, - }, + BaseConfig: BaseConfig{ + ApplicationConfig: baseAppConfig, }, + Registries: baseRegistries, + References: baseMockRef, ShutdownConfig: &ShutdownConfig{ Timeout: "12s", StepTimeout: "2s", @@ -135,90 +141,21 @@ func Test_refresh(t *testing.T) { assert.Equal(t, "dubbo", father.ApplicationConfig.Name) } -func Test_appExternal_refresh(t *testing.T) { +func TestAppExternalRefresh(t *testing.T) { c := &BaseConfig{} - mockMap := map[string]string{} - mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100" - mockMap["dubbo.reference.com.MockService.MockService.retries"] = "10" + mockMap := getMockMap() mockMap["dubbo.reference.com.MockService.retries"] = "5" - mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10" - mockMap["dubbo.consumer.check"] = "false" - mockMap["dubbo.application.name"] = "dubbo" config.GetEnvInstance().UpdateAppExternalConfigMap(mockMap) mockMap["dubbo.consumer.check"] = "true" config.GetEnvInstance().UpdateExternalConfigMap(mockMap) father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, - Registries: map[string]*RegistryConfig{ - //"shanghai_reg1": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, - "shanghai_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "shanghai_idc", - Address: "127.0.0.2:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg1": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.3:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.4:2181", - Username: "user1", - Password: "pwd1", - }, - }, - References: map[string]*ReferenceConfig{ - "MockService": { - InterfaceName: "com.MockService", - Protocol: "mock", - Cluster: "failover", - Loadbalance: "random", - Retries: "3", - Group: "huadong_idc", - Version: "1.0.0", - Methods: []*MethodConfig{ - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser", - Retries: "2", - Loadbalance: "random", - }, - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser1", - Retries: "2", - Loadbalance: "random", - }, - }, - }, + BaseConfig: BaseConfig{ + ApplicationConfig: baseAppConfig, }, + Registries: baseRegistries, + References: baseMockRef, } c.SetFatherConfig(father) @@ -231,89 +168,22 @@ func Test_appExternal_refresh(t *testing.T) { assert.Equal(t, "dubbo", father.ApplicationConfig.Name) } -func Test_appExternalWithoutId_refresh(t *testing.T) { +func TestAppExternalWithoutIDRefresh(t *testing.T) { c := &BaseConfig{} - mockMap := map[string]string{} - mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100" + mockMap := getMockMap() + delete(mockMap, "dubbo.reference.com.MockService.MockService.retries") mockMap["dubbo.reference.com.MockService.retries"] = "10" - mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10" - mockMap["dubbo.consumer.check"] = "false" - mockMap["dubbo.application.name"] = "dubbo" config.GetEnvInstance().UpdateAppExternalConfigMap(mockMap) mockMap["dubbo.consumer.check"] = "true" config.GetEnvInstance().UpdateExternalConfigMap(mockMap) father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, - Registries: map[string]*RegistryConfig{ - //"shanghai_reg1": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, - "shanghai_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "shanghai_idc", - Address: "127.0.0.2:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg1": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.3:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.4:2181", - Username: "user1", - Password: "pwd1", - }, - }, - References: map[string]*ReferenceConfig{ - "MockService": { - InterfaceName: "com.MockService", - Protocol: "mock", - Cluster: "failover", - Loadbalance: "random", - Retries: "3", - Group: "huadong_idc", - Version: "1.0.0", - Methods: []*MethodConfig{ - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser", - Retries: "3", - Loadbalance: "random", - }, - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser1", - Retries: "2", - Loadbalance: "random", - }, - }, - }, + BaseConfig: BaseConfig{ + ApplicationConfig: baseAppConfig, }, + Registries: baseRegistries, + References: baseMockRef, } c.SetFatherConfig(father) @@ -326,7 +196,7 @@ func Test_appExternalWithoutId_refresh(t *testing.T) { assert.Equal(t, "dubbo", father.ApplicationConfig.Name) } -func Test_refresh_singleRegistry(t *testing.T) { +func TestRefreshSingleRegistry(t *testing.T) { c := &BaseConfig{} mockMap := map[string]string{} mockMap["dubbo.registry.address"] = "mock100://127.0.0.1:2181" @@ -339,42 +209,12 @@ func Test_refresh_singleRegistry(t *testing.T) { father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + BaseConfig: BaseConfig{ + ApplicationConfig: baseAppConfig, + }, Registries: map[string]*RegistryConfig{}, Registry: &RegistryConfig{}, - References: map[string]*ReferenceConfig{ - "MockService": { - InterfaceName: "com.MockService", - Protocol: "mock", - Cluster: "failover", - Loadbalance: "random", - Retries: "3", - Group: "huadong_idc", - Version: "1.0.0", - Methods: []*MethodConfig{ - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser", - Retries: "2", - Loadbalance: "random", - }, - { - InterfaceId: "MockService", - InterfaceName: "com.MockService", - Name: "GetUser1", - Retries: "2", - Loadbalance: "random", - }, - }, - }, - }, + References: baseMockRef, } c.SetFatherConfig(father) @@ -387,14 +227,11 @@ func Test_refresh_singleRegistry(t *testing.T) { assert.Equal(t, "dubbo", father.ApplicationConfig.Name) } -func Test_refreshProvider(t *testing.T) { +func TestRefreshProvider(t *testing.T) { c := &BaseConfig{} - mockMap := map[string]string{} - mockMap["dubbo.registries.shanghai_reg1.protocol"] = "mock100" + mockMap := getMockMap() + delete(mockMap, "dubbo.reference.com.MockService.MockService.retries") mockMap["dubbo.service.com.MockService.MockService.retries"] = "10" - mockMap["dubbo.com.MockService.MockService.GetUser.retries"] = "10" - mockMap["dubbo.consumer.check"] = "false" - mockMap["dubbo.application.name"] = "dubbo" mockMap["dubbo.protocols.jsonrpc1.name"] = "jsonrpc" mockMap["dubbo.protocols.jsonrpc1.ip"] = "127.0.0.1" mockMap["dubbo.protocols.jsonrpc1.port"] = "20001" @@ -402,48 +239,10 @@ func Test_refreshProvider(t *testing.T) { config.GetEnvInstance().UpdateExternalConfigMap(mockMap) father := &ProviderConfig{ - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, - Registries: map[string]*RegistryConfig{ - //"shanghai_reg1": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, - "shanghai_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "shanghai_idc", - Address: "127.0.0.2:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg1": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.3:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.4:2181", - Username: "user1", - Password: "pwd1", - }, + BaseConfig: BaseConfig{ + ApplicationConfig: baseAppConfig, }, + Registries: baseRegistries, Services: map[string]*ServiceConfig{ "MockService": { InterfaceName: "com.MockService", @@ -459,13 +258,14 @@ func Test_refreshProvider(t *testing.T) { InterfaceName: "com.MockService", Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, - {InterfaceId: "MockService", + { + InterfaceId: "MockService", InterfaceName: "com.MockService", Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, }, }, @@ -482,7 +282,7 @@ func Test_refreshProvider(t *testing.T) { assert.Equal(t, "20001", father.Protocols["jsonrpc1"].Port) } -func Test_startConfigCenter(t *testing.T) { +func TestStartConfigCenter(t *testing.T) { extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory { return &config_center.MockDynamicConfigurationFactory{} }) @@ -499,7 +299,7 @@ func Test_startConfigCenter(t *testing.T) { assert.Equal(t, "ikurento.com", v) } -func Test_initializeStruct(t *testing.T) { +func TestInitializeStruct(t *testing.T) { testConsumerConfig := &ConsumerConfig{} tp := reflect.TypeOf(ConsumerConfig{}) v := reflect.New(tp) @@ -509,7 +309,7 @@ func Test_initializeStruct(t *testing.T) { reflect.ValueOf(testConsumerConfig).Elem().Set(v.Elem()) assert.Condition(t, func() (success bool) { - return testConsumerConfig.ApplicationConfig != nil + return testConsumerConfig.Registry != nil }) assert.Condition(t, func() (success bool) { return testConsumerConfig.Registries != nil diff --git a/config/config_loader.go b/config/config_loader.go index c84817eaea9d2ba825c96bdb7b2945018b523114..d5f8c68c1bf35c40c09d7d15bae4b6b9f161e9e7 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -21,6 +21,7 @@ import ( "fmt" "log" "os" + "sync" "time" ) @@ -33,15 +34,21 @@ 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/observer/dispatcher" ) var ( - consumerConfig *ConsumerConfig - providerConfig *ProviderConfig - metricConfig *MetricConfig - applicationConfig *ApplicationConfig - maxWait = 3 - confRouterFile string + consumerConfig *ConsumerConfig + providerConfig *ProviderConfig + // baseConfig = providerConfig.BaseConfig or consumerConfig + baseConfig *BaseConfig + + // configAccessMutex is used to make sure that xxxxConfig will only be created once if needed. + // it should be used combine with double-check to avoid the race condition + configAccessMutex sync.Mutex + + maxWait = 3 + confRouterFile string ) // loaded consumer & provider config from xxx.yml, and log config from xxx.xml @@ -59,11 +66,19 @@ func init() { if errCon := ConsumerInit(confConFile); errCon != nil { log.Printf("[consumerInit] %#v", errCon) consumerConfig = nil + } else { + // Even though baseConfig has been initialized, we override it + // because we think read from config file is correct config + baseConfig = &consumerConfig.BaseConfig } if errPro := ProviderInit(confProFile); errPro != nil { log.Printf("[providerInit] %#v", errPro) providerConfig = nil + } else { + // Even though baseConfig has been initialized, we override it + // because we think read from config file is correct config + baseConfig = &providerConfig.BaseConfig } } @@ -89,7 +104,7 @@ func loadConsumerConfig() { // init other consumer config conConfigType := consumerConfig.ConfigType for key, value := range extension.GetDefaultConfigReader() { - if conConfigType == nil { + if conConfigType != nil { if v, ok := conConfigType[key]; ok { value = v } @@ -99,9 +114,6 @@ func loadConsumerConfig() { } } - metricConfig = consumerConfig.MetricConfig - applicationConfig = consumerConfig.ApplicationConfig - checkApplicationName(consumerConfig.ApplicationConfig) if err := configCenterRefreshConsumer(); err != nil { logger.Errorf("[consumer config center refresh] %#v", err) @@ -122,14 +134,14 @@ func loadConsumerConfig() { ref.Implement(rpcService) } - //wait for invoker is available, if wait over default 3s, then panic + // wait for invoker is available, if wait over default 3s, then panic var count int checkok := true for { for _, refconfig := range consumerConfig.References { if (refconfig.Check != nil && *refconfig.Check) || (refconfig.Check == nil && consumerConfig.Check != nil && *consumerConfig.Check) || - (refconfig.Check == nil && consumerConfig.Check == nil) { //default to true + (refconfig.Check == nil && consumerConfig.Check == nil) { // default to true if refconfig.invoker != nil && !refconfig.invoker.IsAvailable() { @@ -174,15 +186,12 @@ func loadProviderConfig() { } } - // so, you should know that the consumer's config will be override - metricConfig = providerConfig.MetricConfig - applicationConfig = providerConfig.ApplicationConfig - checkApplicationName(providerConfig.ApplicationConfig) if err := configCenterRefreshProvider(); err != nil { logger.Errorf("[provider config center refresh] %#v", err) } checkRegistries(providerConfig.Registries, providerConfig.Registry) + for key, svs := range providerConfig.Services { rpcService := GetProviderService(key) if rpcService == nil { @@ -191,6 +200,7 @@ func loadProviderConfig() { } svs.id = key svs.Implement(rpcService) + svs.Protocols = providerConfig.Protocols if err := svs.Export(); err != nil { panic(fmt.Sprintf("service %s export failed! err: %#v", key, err)) } @@ -211,6 +221,15 @@ func Load() { // init router initRouter() + // init the global event dispatcher + extension.SetAndInitGlobalDispatcher(GetBaseConfig().EventDispatcherType) + + // start the metadata report if config set + if err := startMetadataReport(GetApplicationConfig().MetadataType, GetBaseConfig().MetadataReportConfig); err != nil { + logger.Errorf("Provider starts metadata report error, and the error is {%#v}", err) + return + } + // reference config loadConsumerConfig() @@ -233,39 +252,79 @@ func RPCService(service common.RPCService) { // GetMetricConfig find the MetricConfig // if it is nil, create a new one +// we use double-check to reduce race condition +// In general, it will be locked 0 or 1 time. +// So you don't need to worry about the race condition func GetMetricConfig() *MetricConfig { - if metricConfig == nil { - metricConfig = &MetricConfig{} + if GetBaseConfig().MetricConfig == nil { + configAccessMutex.Lock() + defer configAccessMutex.Unlock() + if GetBaseConfig().MetricConfig == nil { + GetBaseConfig().MetricConfig = &MetricConfig{} + } } - return metricConfig + return GetBaseConfig().MetricConfig } // GetApplicationConfig find the application config // if not, we will create one // Usually applicationConfig will be initialized when system start +// we use double-check to reduce race condition +// In general, it will be locked 0 or 1 time. +// So you don't need to worry about the race condition func GetApplicationConfig() *ApplicationConfig { - if applicationConfig == nil { - applicationConfig = &ApplicationConfig{} + if GetBaseConfig().ApplicationConfig == nil { + configAccessMutex.Lock() + defer configAccessMutex.Unlock() + if GetBaseConfig().ApplicationConfig == nil { + GetBaseConfig().ApplicationConfig = &ApplicationConfig{} + } } - return applicationConfig + return GetBaseConfig().ApplicationConfig } // GetProviderConfig find the provider config // if not found, create new one func GetProviderConfig() ProviderConfig { if providerConfig == nil { - logger.Warnf("providerConfig is nil!") - return ProviderConfig{} + if providerConfig == nil { + return ProviderConfig{} + } } return *providerConfig } // GetConsumerConfig find the consumer config // if not found, create new one +// we use double-check to reduce race condition +// In general, it will be locked 0 or 1 time. +// So you don't need to worry about the race condition func GetConsumerConfig() ConsumerConfig { if consumerConfig == nil { - logger.Warnf("consumerConfig is nil!") - return ConsumerConfig{} + if consumerConfig == nil { + return ConsumerConfig{} + } } return *consumerConfig } + +func GetBaseConfig() *BaseConfig { + if baseConfig == nil { + configAccessMutex.Lock() + defer configAccessMutex.Unlock() + if baseConfig == nil { + baseConfig = &BaseConfig{ + MetricConfig: &MetricConfig{}, + ConfigCenterConfig: &ConfigCenterConfig{}, + Remotes: make(map[string]*RemoteConfig, 0), + ApplicationConfig: &ApplicationConfig{}, + ServiceDiscoveries: make(map[string]*ServiceDiscoveryConfig, 0), + } + } + } + return baseConfig +} + +func IsProvider() bool { + return providerConfig != nil +} diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 6368fcbd2c7bc675231e7b7835750f26743708af..01d2ca812a278ee8fb80feb584673e2ebe470a01 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -24,6 +24,7 @@ import ( import ( "github.com/stretchr/testify/assert" + "go.uber.org/atomic" ) import ( @@ -36,10 +37,13 @@ import ( "github.com/apache/dubbo-go/config_center" ) +const mockConsumerConfigPath = "./testdata/consumer_config.yml" +const mockProviderConfigPath = "./testdata/provider_config.yml" + func TestConfigLoader(t *testing.T) { - conPath, err := filepath.Abs("./testdata/consumer_config.yml") + conPath, err := filepath.Abs(mockConsumerConfigPath) assert.NoError(t, err) - proPath, err := filepath.Abs("./testdata/provider_config.yml") + proPath, err := filepath.Abs(mockProviderConfigPath) assert.NoError(t, err) assert.Nil(t, consumerConfig) @@ -90,7 +94,7 @@ func TestLoad(t *testing.T) { func TestLoadWithSingleReg(t *testing.T) { doInitConsumerWithSingleRegistry() - doInitProviderWithSingleRegistry() + mockInitProviderWithSingleRegistry() ms := &MockService{} SetConsumerService(ms) @@ -152,7 +156,7 @@ func TestConfigLoaderWithConfigCenter(t *testing.T) { conPath, err := filepath.Abs("./testdata/consumer_config_with_configcenter.yml") assert.NoError(t, err) - proPath, err := filepath.Abs("./testdata/provider_config.yml") + proPath, err := filepath.Abs(mockProviderConfigPath) assert.NoError(t, err) assert.Nil(t, consumerConfig) @@ -205,7 +209,7 @@ func TestConfigLoaderWithConfigCenterSingleRegistry(t *testing.T) { conPath, err := filepath.Abs("./testdata/consumer_config_with_configcenter.yml") assert.NoError(t, err) - proPath, err := filepath.Abs("./testdata/provider_config.yml") + proPath, err := filepath.Abs(mockProviderConfigPath) assert.NoError(t, err) assert.Nil(t, consumerConfig) @@ -233,3 +237,66 @@ func TestConfigLoaderWithConfigCenterSingleRegistry(t *testing.T) { assert.Equal(t, "mock://127.0.0.1:2182", consumerConfig.Registries[constant.DEFAULT_KEY].Address) } + +func TestGetBaseConfig(t *testing.T) { + bc := GetBaseConfig() + assert.NotNil(t, bc) + _, found := bc.GetRemoteConfig("mock") + assert.False(t, found) +} + +// mockInitProviderWithSingleRegistry will init a mocked providerConfig +func mockInitProviderWithSingleRegistry() { + providerConfig = &ProviderConfig{ + BaseConfig: BaseConfig{ + ApplicationConfig: &ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "1.0.0", + Owner: "dubbo", + Environment: "test"}, + }, + + Registry: &RegistryConfig{ + Address: "mock://127.0.0.1:2181", + Username: "user1", + Password: "pwd1", + }, + Registries: map[string]*RegistryConfig{}, + + Services: map[string]*ServiceConfig{ + "MockService": { + InterfaceName: "com.MockService", + Protocol: "mock", + Cluster: "failover", + Loadbalance: "random", + Retries: "3", + Group: "huadong_idc", + Version: "1.0.0", + Methods: []*MethodConfig{ + { + Name: "GetUser", + Retries: "2", + LoadBalance: "random", + Weight: 200, + }, + { + Name: "GetUser1", + Retries: "2", + LoadBalance: "random", + Weight: 200, + }, + }, + exported: new(atomic.Bool), + }, + }, + Protocols: map[string]*ProtocolConfig{ + "mock": { + Name: "mock", + Ip: "127.0.0.1", + Port: "20000", + }, + }, + } +} diff --git a/config/config_utils.go b/config/config_utils.go index 6bc574a546ebad548aaa15ce7dc9bcf68b95c3a1..5759ff34f6e5673c4a6de8af3b4e1a34a2e7af4c 100644 --- a/config/config_utils.go +++ b/config/config_utils.go @@ -18,6 +18,7 @@ package config import ( + "fmt" "regexp" "strings" ) @@ -30,50 +31,35 @@ func mergeValue(str1, str2, def string) string { if str1 == "" && str2 == "" { return def } - str := "," + strings.Trim(str1, ",") - if str1 == "" { - str = "," + strings.Trim(str2, ",") - } else if str2 != "" { - str = str + "," + strings.Trim(str2, ",") - } + s1 := strings.Split(str1, ",") + s2 := strings.Split(str2, ",") + str := "," + strings.Join(append(s1, s2...), ",") defKey := strings.Contains(str, ","+constant.DEFAULT_KEY) if !defKey { str = "," + constant.DEFAULT_KEY + str } str = strings.TrimPrefix(strings.Replace(str, ","+constant.DEFAULT_KEY, ","+def, -1), ",") + return removeMinus(strings.Split(str, ",")) +} - strArr := strings.Split(str, ",") - strMap := make(map[string][]int) - for k, v := range strArr { - add := true +func removeMinus(strArr []string) string { + if len(strArr) == 0 { + return "" + } + var normalStr string + var minusStrArr []string + for _, v := range strArr { if strings.HasPrefix(v, "-") { - v = v[1:] - add = false - } - if _, ok := strMap[v]; !ok { - if add { - strMap[v] = []int{1, k} - } + minusStrArr = append(minusStrArr, v[1:]) } else { - if add { - strMap[v][0] += 1 - strMap[v] = append(strMap[v], k) - } else { - strMap[v][0] -= 1 - strMap[v] = strMap[v][:len(strMap[v])-1] - } + normalStr += fmt.Sprintf(",%s", v) } } - strArr = make([]string, len(strArr)) - for key, value := range strMap { - if value[0] == 0 { - continue - } - for i := 1; i < len(value); i++ { - strArr[value[i]] = key - } + normalStr = strings.Trim(normalStr, ",") + for _, v := range minusStrArr { + normalStr = strings.Replace(normalStr, v, "", 1) } reg := regexp.MustCompile("[,]+") - str = reg.ReplaceAllString(strings.Join(strArr, ","), ",") - return strings.Trim(str, ",") + normalStr = reg.ReplaceAllString(strings.Trim(normalStr, ","), ",") + return normalStr } diff --git a/config/config_utils_test.go b/config/config_utils_test.go index 5170b90c83a0f31bbbe1b5de5bca9b8dc5869ac6..81fc3a3721d5915ec3652e03f80ec203e170d1fa 100644 --- a/config/config_utils_test.go +++ b/config/config_utils_test.go @@ -41,3 +41,23 @@ func TestMergeValue(t *testing.T) { str = mergeValue("", "default,-b,e,f", "a,b") assert.Equal(t, "a,e,f", str) } + +func TestRemoveMinus(t *testing.T) { + strList := removeMinus([]string{}) + assert.Equal(t, strList, "") + + strList = removeMinus([]string{"a", "b", "c", "d", "-a"}) + assert.Equal(t, strList, "b,c,d") + + strList = removeMinus([]string{"a", "b", "c", "d", "-a", "-b"}) + assert.Equal(t, strList, "c,d") + + strList = removeMinus([]string{"a", "b", "c", "-c", "-a", "-b"}) + assert.Equal(t, strList, "") + + strList = removeMinus([]string{"b", "a", "-c", "c"}) + assert.Equal(t, strList, "b,a") + + strList = removeMinus([]string{"c", "b", "a", "d", "c", "-c", "-a", "e", "f"}) + assert.Equal(t, strList, "b,d,c,e,f") +} diff --git a/config/consumer_config.go b/config/consumer_config.go index 1453628ab5e24a3607183c349d44e9440868ab60..48f29f0e70028a7c057ee3831b45afa72446f3d0 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -42,20 +42,18 @@ import ( type ConsumerConfig struct { BaseConfig `yaml:",inline"` Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` - // application - ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"` - // client Connect_Timeout string `default:"100ms" yaml:"connect_timeout" json:"connect_timeout,omitempty" property:"connect_timeout"` ConnectTimeout time.Duration + Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"` + Registries map[string]*RegistryConfig `default:"{}" yaml:"registries" json:"registries" property:"registries"` + Request_Timeout string `yaml:"request_timeout" default:"5s" json:"request_timeout,omitempty" property:"request_timeout"` RequestTimeout time.Duration ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"` Check *bool `yaml:"check" json:"check,omitempty" property:"check"` - Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"` - Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"` References map[string]*ReferenceConfig `yaml:"references" json:"references,omitempty" property:"references"` ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"` FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` diff --git a/config/graceful_shutdown_config_test.go b/config/graceful_shutdown_config_test.go index 583ed70b838a8271a47e180ee3c6eb32cbb46984..80eb5317386f4e9d966f7a9f07635a810727d77c 100644 --- a/config/graceful_shutdown_config_test.go +++ b/config/graceful_shutdown_config_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestShutdownConfig_GetTimeout(t *testing.T) { +func TestShutdownConfigGetTimeout(t *testing.T) { config := ShutdownConfig{} assert.False(t, config.RejectRequest) assert.False(t, config.RequestsFinished) diff --git a/config/graceful_shutdown_signal_darwin.go b/config/graceful_shutdown_signal_darwin.go index 6f1fa982a30125096c553e65c13bae1a413ea141..1a557dd3ed32cfe571216f61a5a6f8fe064bb254 100644 --- a/config/graceful_shutdown_signal_darwin.go +++ b/config/graceful_shutdown_signal_darwin.go @@ -26,7 +26,7 @@ var ( // ShutdownSignals receives shutdown signals to process ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, - syscall.SIGABRT, syscall.SIGSYS} + syscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM} // DumpHeapShutdownSignals receives shutdown signals to process DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, diff --git a/config/graceful_shutdown_signal_linux.go b/config/graceful_shutdown_signal_linux.go index 6f1fa982a30125096c553e65c13bae1a413ea141..1a557dd3ed32cfe571216f61a5a6f8fe064bb254 100644 --- a/config/graceful_shutdown_signal_linux.go +++ b/config/graceful_shutdown_signal_linux.go @@ -26,7 +26,7 @@ var ( // ShutdownSignals receives shutdown signals to process ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGSTOP, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, - syscall.SIGABRT, syscall.SIGSYS} + syscall.SIGABRT, syscall.SIGSYS, syscall.SIGTERM} // DumpHeapShutdownSignals receives shutdown signals to process DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, diff --git a/config/graceful_shutdown_signal_windows.go b/config/graceful_shutdown_signal_windows.go index 3136e5ae15081f026e8a6e602a5174e1d396abf7..89edd27b184e3fddda5e794f686b86397c7019bb 100644 --- a/config/graceful_shutdown_signal_windows.go +++ b/config/graceful_shutdown_signal_windows.go @@ -26,7 +26,7 @@ var ( // ShutdownSignals receives shutdown signals to process ShutdownSignals = []os.Signal{os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, - syscall.SIGABRT} + syscall.SIGABRT, syscall.SIGTERM} // DumpHeapShutdownSignals receives shutdown signals to process DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, syscall.SIGABRT} diff --git a/config/instance/metadata_report.go b/config/instance/metadata_report.go index 4c935d733283e6a79523dd0f35be60f092351dbb..8e833dd70bcc0db8e65cd8703f2bc1859432a887 100644 --- a/config/instance/metadata_report.go +++ b/config/instance/metadata_report.go @@ -24,18 +24,35 @@ import ( import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/metadata" + "github.com/apache/dubbo-go/metadata/report" ) var ( - instance metadata.MetadataReport - once sync.Once + instance report.MetadataReport + reportUrl common.URL + once sync.Once ) -// GetMetadataReportInstance gets metadata report instance by @url -func GetMetadataReportInstance(url *common.URL) metadata.MetadataReport { +// GetMetadataReportInstance will return the instance in lazy mode. Be careful the instance create will only +// execute once. +func GetMetadataReportInstance(selectiveUrl ...*common.URL) report.MetadataReport { once.Do(func() { - instance = extension.GetMetadataReportFactory(url.Protocol).CreateMetadataReport(url) + var url *common.URL + if len(selectiveUrl) > 0 { + url = selectiveUrl[0] + instance = extension.GetMetadataReportFactory(url.Protocol).CreateMetadataReport(url) + reportUrl = *url + } }) return instance } + +// GetMetadataReportUrl will return the report instance url +func GetMetadataReportUrl() common.URL { + return reportUrl +} + +// SetMetadataReportUrl will only can be used by unit test to mock url +func SetMetadataReportUrl(url common.URL) { + reportUrl = url +} diff --git a/config/instance/metadata_report_test.go b/config/instance/metadata_report_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d489af048055b362f2fa68a8963dd2cdf9632945 --- /dev/null +++ b/config/instance/metadata_report_test.go @@ -0,0 +1,85 @@ +/* + * 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 instance + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/report/factory" +) + +func TestGetMetadataReportInstance(t *testing.T) { + extension.SetMetadataReportFactory("mock", func() factory.MetadataReportFactory { + return &mockMetadataReportFactory{} + }) + u, _ := common.NewURL("mock://127.0.0.1") + rpt := GetMetadataReportInstance(&u) + assert.NotNil(t, rpt) +} + +type mockMetadataReportFactory struct { +} + +func (m *mockMetadataReportFactory) CreateMetadataReport(*common.URL) report.MetadataReport { + return &mockMetadataReport{} +} + +type mockMetadataReport struct { +} + +func (m mockMetadataReport) StoreProviderMetadata(*identifier.MetadataIdentifier, string) error { + panic("implement me") +} + +func (m mockMetadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error { + panic("implement me") +} + +func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error { + panic("implement me") +} + +func (m mockMetadataReport) RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error { + panic("implement me") +} + +func (m mockMetadataReport) GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error) { + panic("implement me") +} + +func (m mockMetadataReport) SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, string) error { + panic("implement me") +} + +func (m mockMetadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error) { + panic("implement me") +} + +func (m mockMetadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) (string, error) { + panic("implement me") +} diff --git a/config/metadata_report_config.go b/config/metadata_report_config.go index 11fb0cd65cf5500c09c51268115e5f7c60916657..6d319e5ecb8007e06dcf790fff145bfab754df3d 100644 --- a/config/metadata_report_config.go +++ b/config/metadata_report_config.go @@ -34,13 +34,10 @@ import ( // MethodConfig is method level configuration type MetadataReportConfig struct { - Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty"` - Address string `yaml:"address" json:"address,omitempty" property:"address"` - Username string `yaml:"username" json:"username,omitempty" property:"username"` - Password string `yaml:"password" json:"password,omitempty" property:"password"` - Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` - TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second - Group string `yaml:"group" json:"group,omitempty" property:"group"` + Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty"` + RemoteRef string `required:"true" yaml:"remote_ref" json:"remote_ref,omitempty"` + Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` + Group string `yaml:"group" json:"group,omitempty" property:"group"` } // nolint @@ -70,18 +67,24 @@ func (c *MetadataReportConfig) ToUrl() (*common.URL, error) { } } - url, err := common.NewURL(c.Address, + rc, ok := GetBaseConfig().GetRemoteConfig(c.RemoteRef) + + if !ok { + return nil, perrors.New("Could not find out the remote ref config, name: " + c.RemoteRef) + } + + res, err := common.NewURL(rc.Address, common.WithParams(urlMap), - common.WithUsername(c.Username), - common.WithPassword(c.Password), - common.WithLocation(c.Address), + common.WithUsername(rc.Username), + common.WithPassword(rc.Password), + common.WithLocation(rc.Address), common.WithProtocol(c.Protocol), ) - if err != nil || len(url.Protocol) == 0 { + if err != nil || len(res.Protocol) == 0 { return nil, perrors.New("Invalid MetadataReportConfig.") } - url.SetParam("metadata", url.Protocol) - return &url, nil + res.SetParam("metadata", res.Protocol) + return &res, nil } func (c *MetadataReportConfig) IsValid() bool { @@ -90,14 +93,12 @@ func (c *MetadataReportConfig) IsValid() bool { // StartMetadataReport: The entry of metadata report start func startMetadataReport(metadataType string, metadataReportConfig *MetadataReportConfig) error { - if metadataReportConfig == nil || metadataReportConfig.IsValid() { + if metadataReportConfig == nil || !metadataReportConfig.IsValid() { return nil } - if metadataType == constant.METACONFIG_REMOTE { - return perrors.New("No MetadataConfig found, you must specify the remote Metadata Center address when 'metadata=remote' is enabled.") - } else if metadataType == constant.METACONFIG_REMOTE && len(metadataReportConfig.Address) == 0 { - return perrors.New("MetadataConfig address can not be empty.") + if metadataType == constant.METACONFIG_REMOTE && len(metadataReportConfig.RemoteRef) == 0 { + return perrors.New("MetadataConfig remote ref can not be empty.") } if url, err := metadataReportConfig.ToUrl(); err == nil { diff --git a/config/metadata_report_config_test.go b/config/metadata_report_config_test.go index 635feecc2d433366534566d184e058eb54a881ed..1c585ee79d58826b227df52574b3403639856306 100644 --- a/config/metadata_report_config_test.go +++ b/config/metadata_report_config_test.go @@ -24,12 +24,15 @@ import ( ) func TestMetadataReportConfig_ToUrl(t *testing.T) { - metadataReportConfig := MetadataReportConfig{ - Protocol: "mock", + GetBaseConfig().Remotes["mock"] = &RemoteConfig{ Address: "127.0.0.1:2181", Username: "test", Password: "test", TimeoutStr: "3s", + } + metadataReportConfig := MetadataReportConfig{ + Protocol: "mock", + RemoteRef: "mock", Params: map[string]string{ "k": "v", }, diff --git a/config/method_config.go b/config/method_config.go index e64773eb135b2f9ec55377bded815147e2e192af..b64306fd6aa865d219506ea2722067619b00fea7 100644 --- a/config/method_config.go +++ b/config/method_config.go @@ -25,13 +25,13 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -// MethodConfig ... +// MethodConfig defines method config type MethodConfig struct { InterfaceId string InterfaceName string Name string `yaml:"name" json:"name,omitempty" property:"name"` Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` - Loadbalance string `yaml:"loadbalance" json:"loadbalance,omitempty" property:"loadbalance"` + LoadBalance string `yaml:"loadbalance" json:"loadbalance,omitempty" property:"loadbalance"` Weight int64 `yaml:"weight" json:"weight,omitempty" property:"weight"` TpsLimitInterval string `yaml:"tps.limit.interval" json:"tps.limit.interval,omitempty" property:"tps.limit.interval"` TpsLimitRate string `yaml:"tps.limit.rate" json:"tps.limit.rate,omitempty" property:"tps.limit.rate"` diff --git a/config/provider_config.go b/config/provider_config.go index 97d037ede008b5fc8cab2febb2177a4e8c3dbd1b..7cd3c1e98bfb8c35abb2b414b782ec709d0a8d0d 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -28,7 +28,6 @@ import ( import ( "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/common/yaml" ) @@ -38,21 +37,18 @@ import ( // ProviderConfig is the default configuration of service provider type ProviderConfig struct { - BaseConfig `yaml:",inline"` - Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` - ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"` - // metadata-report - MetadataReportConfig *MetadataReportConfig `yaml:"metadata_report" json:"metadata_report,omitempty" property:"metadata_report"` + BaseConfig `yaml:",inline"` + Filter string `yaml:"filter" json:"filter,omitempty" property:"filter"` + ProxyFactory string `yaml:"proxy_factory" default:"default" json:"proxy_factory,omitempty" property:"proxy_factory"` + Services map[string]*ServiceConfig `yaml:"services" json:"services,omitempty" property:"services"` + Protocols map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"` + ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" ` + FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` + ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` + ConfigType map[string]string `yaml:"config_type" json:"config_type,omitempty" property:"config_type"` - ApplicationConfig *ApplicationConfig `yaml:"application" json:"application,omitempty" property:"application"` - Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"` - Registries map[string]*RegistryConfig `yaml:"registries" json:"registries,omitempty" property:"registries"` - Services map[string]*ServiceConfig `yaml:"services" json:"services,omitempty" property:"services"` - Protocols map[string]*ProtocolConfig `yaml:"protocols" json:"protocols,omitempty" property:"protocols"` - ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf" ` - FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` - ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` - ConfigType map[string]string `yaml:"config_type" json:"config_type,omitempty" property:"config_type"` + Registry *RegistryConfig `yaml:"registry" json:"registry,omitempty" property:"registry"` + Registries map[string]*RegistryConfig `default:"{}" yaml:"registries" json:"registries" property:"registries"` } // UnmarshalYAML unmarshals the ProviderConfig by @unmarshal function @@ -89,25 +85,20 @@ func ProviderInit(confProFile string) error { } providerConfig.fileStream = bytes.NewBuffer(fileStream) - //set method interfaceId & interfaceName + // set method interfaceId & interfaceName for k, v := range providerConfig.Services { - //set id for reference + // set id for reference for _, n := range providerConfig.Services[k].Methods { n.InterfaceName = v.InterfaceName n.InterfaceId = k } } - //start the metadata report if config set - if err := startMetadataReport(providerConfig.ApplicationConfig.MetadataType, providerConfig.MetadataReportConfig); err != nil { - return perrors.WithMessagef(err, "Provider starts metadata report error, and the error is {%#v}", err) - } - logger.Debugf("provider config{%#v}\n", providerConfig) return nil } func configCenterRefreshProvider() error { - //fresh it + // fresh it if providerConfig.ConfigCenterConfig != nil { providerConfig.fatherConfig = providerConfig if err := providerConfig.startConfigCenter(); err != nil { diff --git a/config/reference_config.go b/config/reference_config.go index 5b7a8e9eac676e10276775cab327ae4de1eddf86..e9a895d57a90d9fd2d5d08dcd8706e5b2d058174 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -55,6 +55,7 @@ type ReferenceConfig struct { Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` Group string `yaml:"group" json:"group,omitempty" property:"group"` Version string `yaml:"version" json:"version,omitempty" property:"version"` + ProvideBy string `yaml:"provide_by" json:"provide_by,omitempty" property:"provide_by"` Methods []*MethodConfig `yaml:"methods" json:"methods,omitempty" property:"methods"` Async bool `yaml:"async" json:"async,omitempty" property:"async"` Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` @@ -144,6 +145,8 @@ func (c *ReferenceConfig) Refer(_ interface{}) { regUrl = u } } + + // TODO(decouple from directory, config should not depend on directory module) if regUrl != nil { cluster := extension.GetCluster("registryAware") c.invoker = cluster.Join(directory.NewStaticDirectory(invokers)) @@ -188,6 +191,7 @@ func (c *ReferenceConfig) getUrlMap() url.Values { urlMap.Set(constant.VERSION_KEY, c.Version) urlMap.Set(constant.GENERIC_KEY, strconv.FormatBool(c.Generic)) urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) + urlMap.Set(constant.PROVIDER_BY, c.ProvideBy) urlMap.Set(constant.RELEASE_KEY, "dubbo-golang-"+constant.Version) urlMap.Set(constant.SIDE_KEY, (common.RoleType(common.CONSUMER)).Role()) @@ -216,7 +220,7 @@ func (c *ReferenceConfig) getUrlMap() url.Values { urlMap.Set(constant.REFERENCE_FILTER_KEY, mergeValue(consumerConfig.Filter, c.Filter, defaultReferenceFilter)) for _, v := range c.Methods { - urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.Loadbalance) + urlMap.Set("methods."+v.Name+"."+constant.LOADBALANCE_KEY, v.LoadBalance) urlMap.Set("methods."+v.Name+"."+constant.RETRIES_KEY, v.Retries) urlMap.Set("methods."+v.Name+"."+constant.STICKY_KEY, strconv.FormatBool(v.Sticky)) if len(v.RequestTimeout) != 0 { diff --git a/config/reference_config_test.go b/config/reference_config_test.go index 7a65e55f09c997cb49b83f1f185faf9338cf0f5a..45cdb2dfaca32f918bc067cf489a3c6fc4820dbc 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -38,13 +38,16 @@ var regProtocol protocol.Protocol func doInitConsumer() { consumerConfig = &ConsumerConfig{ - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + BaseConfig: BaseConfig{ + ApplicationConfig: &ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "2.6.0", + Owner: "dubbo", + Environment: "test"}, + }, + Registries: map[string]*RegistryConfig{ "shanghai_reg1": { Protocol: "mock", @@ -79,6 +82,7 @@ func doInitConsumer() { Password: "pwd1", }, }, + References: map[string]*ReferenceConfig{ "MockService": { id: "MockProvider", @@ -99,12 +103,12 @@ func doInitConsumer() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Sticky: true, }, }, @@ -123,6 +127,7 @@ func (m *MockProvider) Reference() string { } func (m *MockProvider) CallBack(res common.CallbackResponse) { + // CallBack is a mock function. to implement the interface } func doInitConsumerAsync() { @@ -135,19 +140,23 @@ func doInitConsumerAsync() { func doInitConsumerWithSingleRegistry() { consumerConfig = &ConsumerConfig{ - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + BaseConfig: BaseConfig{ + ApplicationConfig: &ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "2.6.0", + Owner: "dubbo", + Environment: "test"}, + }, + Registry: &RegistryConfig{ Address: "mock://27.0.0.1:2181", Username: "user1", Password: "pwd1", }, Registries: map[string]*RegistryConfig{}, + References: map[string]*ReferenceConfig{ "MockService": { Params: map[string]string{ @@ -165,12 +174,12 @@ func doInitConsumerWithSingleRegistry() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", }, }, }, @@ -178,7 +187,7 @@ func doInitConsumerWithSingleRegistry() { } } -func Test_ReferMultireg(t *testing.T) { +func TestReferMultireg(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) @@ -191,7 +200,7 @@ func Test_ReferMultireg(t *testing.T) { consumerConfig = nil } -func Test_Refer(t *testing.T) { +func TestRefer(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) @@ -205,7 +214,7 @@ func Test_Refer(t *testing.T) { consumerConfig = nil } -func Test_ReferAsync(t *testing.T) { +func TestReferAsync(t *testing.T) { doInitConsumerAsync() extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) @@ -220,7 +229,7 @@ func Test_ReferAsync(t *testing.T) { consumerConfig = nil } -func Test_ReferP2P(t *testing.T) { +func TestReferP2P(t *testing.T) { doInitConsumer() extension.SetProtocol("dubbo", GetProtocol) m := consumerConfig.References["MockService"] @@ -234,7 +243,7 @@ func Test_ReferP2P(t *testing.T) { consumerConfig = nil } -func Test_ReferMultiP2P(t *testing.T) { +func TestReferMultiP2P(t *testing.T) { doInitConsumer() extension.SetProtocol("dubbo", GetProtocol) m := consumerConfig.References["MockService"] @@ -248,7 +257,7 @@ func Test_ReferMultiP2P(t *testing.T) { consumerConfig = nil } -func Test_ReferMultiP2PWithReg(t *testing.T) { +func TestReferMultiP2PWithReg(t *testing.T) { doInitConsumer() extension.SetProtocol("dubbo", GetProtocol) extension.SetProtocol("registry", GetProtocol) @@ -263,7 +272,7 @@ func Test_ReferMultiP2PWithReg(t *testing.T) { consumerConfig = nil } -func Test_Implement(t *testing.T) { +func TestImplement(t *testing.T) { doInitConsumer() extension.SetProtocol("registry", GetProtocol) extension.SetCluster("registryAware", cluster_impl.NewRegistryAwareCluster) @@ -276,7 +285,7 @@ func Test_Implement(t *testing.T) { consumerConfig = nil } -func Test_Forking(t *testing.T) { +func TestForking(t *testing.T) { doInitConsumer() extension.SetProtocol("dubbo", GetProtocol) extension.SetProtocol("registry", GetProtocol) @@ -293,7 +302,7 @@ func Test_Forking(t *testing.T) { consumerConfig = nil } -func Test_Sticky(t *testing.T) { +func TestSticky(t *testing.T) { doInitConsumer() extension.SetProtocol("dubbo", GetProtocol) extension.SetProtocol("registry", GetProtocol) @@ -332,4 +341,6 @@ func (*mockRegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter return protocol.NewBaseExporter("test", invoker, &sync.Map{}) } -func (*mockRegistryProtocol) Destroy() {} +func (*mockRegistryProtocol) Destroy() { + // Destroy is a mock function +} diff --git a/config/registry_config_test.go b/config/registry_config_test.go index 6c2fed605d6c50b483f7ad2900e5a483b3986e1b..6e5dedc34ff5489fc190841bce73cd015eb78132 100644 --- a/config/registry_config_test.go +++ b/config/registry_config_test.go @@ -29,7 +29,7 @@ import ( "github.com/apache/dubbo-go/common" ) -func Test_loadRegistries(t *testing.T) { +func TestLoadRegistries(t *testing.T) { target := "shanghai1" regs := map[string]*RegistryConfig{ @@ -47,7 +47,7 @@ func Test_loadRegistries(t *testing.T) { assert.Equal(t, "127.0.0.2:2181,128.0.0.1:2181", urls[0].Location) } -func Test_loadRegistries1(t *testing.T) { +func TestLoadRegistries1(t *testing.T) { target := "shanghai1" regs := map[string]*RegistryConfig{ diff --git a/config/remote_config.go b/config/remote_config.go new file mode 100644 index 0000000000000000000000000000000000000000..5e0330c571715d99e63688ee944c61f8e48117bb --- /dev/null +++ b/config/remote_config.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 config + +import ( + "time" +) + +import ( + "github.com/apache/dubbo-go/common/logger" +) + +// RemoteConfig: usually we need some middleware, including nacos, zookeeper +// this represents an instance of this middleware +// so that other module, like config center, registry could reuse the config +// but now, only metadata report, metadata service, service discovery use this structure +type RemoteConfig struct { + Address string `yaml:"address" json:"address,omitempty"` + TimeoutStr string `default:"5s" yaml:"timeout" json:"timeout,omitempty"` + Username string `yaml:"username" json:"username,omitempty" property:"username"` + Password string `yaml:"password" json:"password,omitempty" property:"password"` + Params map[string]string `yaml:"params" json:"address,omitempty"` +} + +// Timeout return timeout duration. +// if the configure is invalid, or missing, the default value 5s will be returned +func (rc *RemoteConfig) Timeout() time.Duration { + if res, err := time.ParseDuration(rc.TimeoutStr); err == nil { + return res + } + logger.Errorf("Could not parse the timeout string to Duration: %s, the default value will be returned", rc.TimeoutStr) + return 5 * time.Second +} + +// GetParam will return the value of the key. If not found, def will be return; +// def => default value +func (rc *RemoteConfig) GetParam(key string, def string) string { + param, ok := rc.Params[key] + if !ok { + return def + } + return param +} diff --git a/config/remote_config_test.go b/config/remote_config_test.go new file mode 100644 index 0000000000000000000000000000000000000000..82535fd60b932aecb7c6c3ee8206130fad9e7161 --- /dev/null +++ b/config/remote_config_test.go @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +import ( + "testing" +) +import ( + "github.com/stretchr/testify/assert" +) + +func TestRemoteConfig_GetParam(t *testing.T) { + rc := &RemoteConfig{ + Params: make(map[string]string, 1), + } + + def := "default value" + key := "key" + value := rc.GetParam(key, def) + assert.Equal(t, def, value) + + actualVal := "actual value" + rc.Params[key] = actualVal + + value = rc.GetParam(key, def) + assert.Equal(t, actualVal, value) +} diff --git a/config/router_config.go b/config/router_config.go index 0670ee9c20f618021d1d574344a0df85d837bd66..16a2bec918d0f9a2de2174324e78ca21e853dabf 100644 --- a/config/router_config.go +++ b/config/router_config.go @@ -18,16 +18,20 @@ package config import ( + gxset "github.com/dubbogo/gost/container/set" 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" ) +var ( + routerURLSet = gxset.NewSet() +) + // RouterInit Load config file to init router config func RouterInit(confRouterFile string) error { fileRouterFactories := extension.GetFileRouterFactories() @@ -40,10 +44,14 @@ func RouterInit(confRouterFile string) error { r, e := factory.NewFileRouter(bytes) if e == nil { url := r.URL() - directory.AddRouterURLSet(&url) + routerURLSet.Add(&url) return nil } logger.Warnf("router config type %s create fail {%v}\n", k, e) } return perrors.Errorf("no file router exists for parse %s , implement router.FIleRouterFactory please.", confRouterFile) } + +func GetRouterURLSet() *gxset.HashSet { + return routerURLSet +} diff --git a/config/router_config_test.go b/config/router_config_test.go index 2f0a38b2fdf59578c77076680c05b3eca5c26a1c..72e51c1c82562b03736fd0afef79b78d83d6f4f3 100644 --- a/config/router_config_test.go +++ b/config/router_config_test.go @@ -27,7 +27,6 @@ import ( ) import ( - "github.com/apache/dubbo-go/cluster/directory" _ "github.com/apache/dubbo-go/cluster/router/condition" ) @@ -58,10 +57,10 @@ func TestRouterInit(t *testing.T) { errPro := RouterInit(errorTestYML) assert.Error(t, errPro) - assert.Equal(t, 0, directory.GetRouterURLSet().Size()) + assert.Equal(t, 0, routerURLSet.Size()) errPro = RouterInit(testYML) assert.NoError(t, errPro) - assert.Equal(t, 1, directory.GetRouterURLSet().Size()) + assert.Equal(t, 1, routerURLSet.Size()) } diff --git a/config/service_config.go b/config/service_config.go index a49af18c21e4b51fd9214f5f49e9380ec7716690..d77d4bc1bb057145258e73af5404ea69d730a31c 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -74,14 +74,18 @@ type ServiceConfig struct { ParamSign string `yaml:"param.sign" json:"param.sign,omitempty" property:"param.sign"` Tag string `yaml:"tag" json:"tag,omitempty" property:"tag"` + Protocols map[string]*ProtocolConfig unexported *atomic.Bool exported *atomic.Bool rpcService common.RPCService - cacheProtocol protocol.Protocol cacheMutex sync.Mutex + cacheProtocol protocol.Protocol + + exportersLock sync.Mutex + exporters []protocol.Exporter } -// nolint +// Prefix returns dubbo.service.${interface}. func (c *ServiceConfig) Prefix() string { return constant.ServiceConfigPrefix + c.InterfaceName + "." } @@ -95,6 +99,8 @@ func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal((*plain)(c)); err != nil { return err } + c.exported = atomic.NewBool(false) + c.unexported = atomic.NewBool(false) return nil } @@ -108,6 +114,16 @@ func NewServiceConfig(id string, context context.Context) *ServiceConfig { } } +// InitExported will set exported as false atom bool +func (c *ServiceConfig) InitExported() { + c.exported = atomic.NewBool(false) +} + +// IsExport will return whether the service config is exported or not +func (c *ServiceConfig) IsExport() bool { + return c.exported.Load() +} + // Get Random Port func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List { ports := list.New() @@ -126,7 +142,7 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List { return ports } -// Export ... +// Export exports the service func (c *ServiceConfig) Export() error { // TODO: config center start here @@ -143,7 +159,7 @@ func (c *ServiceConfig) Export() error { regUrls := loadRegistries(c.Registry, providerConfig.Registries, common.PROVIDER) urlMap := c.getUrlMap() - protocolConfigs := loadProtocol(c.Protocol, providerConfig.Protocols) + protocolConfigs := loadProtocol(c.Protocol, c.Protocols) if len(protocolConfigs) == 0 { logger.Warnf("The service %v's '%v' protocols don't has right protocolConfigs ", c.InterfaceName, c.Protocol) return nil @@ -179,6 +195,9 @@ func (c *ServiceConfig) Export() error { if len(c.Tag) > 0 { ivkURL.AddParam(constant.Tagkey, c.Tag) } + + var exporter protocol.Exporter + if len(regUrls) > 0 { for _, regUrl := range regUrls { regUrl.SubURL = ivkURL @@ -191,23 +210,47 @@ func (c *ServiceConfig) Export() error { c.cacheMutex.Unlock() invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl) - exporter := c.cacheProtocol.Export(invoker) + exporter = c.cacheProtocol.Export(invoker) if exporter == nil { panic(perrors.New(fmt.Sprintf("Registry protocol new exporter error,registry is {%v},url is {%v}", regUrl, ivkURL))) } } } else { invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*ivkURL) - exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) + exporter = extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) if exporter == nil { panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL))) } } + c.exporters = append(c.exporters, exporter) } + c.exported.Store(true) return nil } -// Implement ... +// Unexport will call unexport of all exporters service config exported +func (c *ServiceConfig) Unexport() { + if !c.exported.Load() { + return + } + if c.unexported.Load() { + return + } + + func() { + c.exportersLock.Lock() + defer c.exportersLock.Unlock() + for _, exporter := range c.exporters { + exporter.Unexport() + } + c.exporters = nil + }() + + c.exported.Store(false) + c.unexported.Store(true) +} + +// Implement only store the @s and return func (c *ServiceConfig) Implement(s common.RPCService) { c.rpcService = s } @@ -262,7 +305,7 @@ func (c *ServiceConfig) getUrlMap() url.Values { for _, v := range c.Methods { prefix := "methods." + v.Name + "." - urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.Loadbalance) + urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.LoadBalance) urlMap.Set(prefix+constant.RETRIES_KEY, v.Retries) urlMap.Set(prefix+constant.WEIGHT_KEY, strconv.FormatInt(v.Weight, 10)) @@ -277,3 +320,16 @@ func (c *ServiceConfig) getUrlMap() url.Values { return urlMap } + +// GetExportedUrls will return the url in service config's exporter +func (c *ServiceConfig) GetExportedUrls() []*common.URL { + if c.exported.Load() { + var urls []*common.URL + for _, exporter := range c.exporters { + url := exporter.GetInvoker().GetUrl() + urls = append(urls, &url) + } + return urls + } + return nil +} diff --git a/config/service_config_test.go b/config/service_config_test.go index e39d32b7f7976cbdc668153bcd901e40dd635556..0f7e404f6e336b8ad254e4007825ecbfcaf9d78b 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -24,6 +24,7 @@ import ( import ( gxnet "github.com/dubbogo/gost/net" "github.com/stretchr/testify/assert" + "go.uber.org/atomic" ) import ( @@ -32,46 +33,14 @@ import ( func doInitProvider() { providerConfig = &ProviderConfig{ - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, - Registries: map[string]*RegistryConfig{ - "shanghai_reg1": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "shanghai_idc", - Address: "127.0.0.1:2181", - Username: "user1", - Password: "pwd1", - }, - "shanghai_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "shanghai_idc", - Address: "127.0.0.2:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg1": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.3:2181", - Username: "user1", - Password: "pwd1", - }, - "hangzhou_reg2": { - Protocol: "mock", - TimeoutStr: "2s", - Group: "hangzhou_idc", - Address: "127.0.0.4:2181", - Username: "user1", - Password: "pwd1", - }, + BaseConfig: BaseConfig{ + ApplicationConfig: &ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "2.6.0", + Owner: "dubbo", + Environment: "test"}, }, Services: map[string]*ServiceConfig{ "MockService": { @@ -87,16 +56,17 @@ func doInitProvider() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, + exported: new(atomic.Bool), }, "MockServiceNoRightProtocol": { InterfaceName: "com.MockService", @@ -111,68 +81,55 @@ func doInitProvider() { { Name: "GetUser", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, { Name: "GetUser1", Retries: "2", - Loadbalance: "random", + LoadBalance: "random", Weight: 200, }, }, + exported: new(atomic.Bool), }, }, - Protocols: map[string]*ProtocolConfig{ - "mock": { - Name: "mock", - Ip: "127.0.0.1", - Port: "20000", - }, - }, - } -} -func doInitProviderWithSingleRegistry() { - providerConfig = &ProviderConfig{ - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, - Registry: &RegistryConfig{ - Address: "mock://127.0.0.1:2181", - Username: "user1", - Password: "pwd1", - }, - Registries: map[string]*RegistryConfig{}, - Services: map[string]*ServiceConfig{ - "MockService": { - InterfaceName: "com.MockService", - Protocol: "mock", - Cluster: "failover", - Loadbalance: "random", - Retries: "3", - Group: "huadong_idc", - Version: "1.0.0", - Methods: []*MethodConfig{ - { - Name: "GetUser", - Retries: "2", - Loadbalance: "random", - Weight: 200, - }, - { - Name: "GetUser1", - Retries: "2", - Loadbalance: "random", - Weight: 200, - }, - }, + Registries: map[string]*RegistryConfig{ + "shanghai_reg1": { + Protocol: "mock", + TimeoutStr: "2s", + Group: "shanghai_idc", + Address: "127.0.0.1:2181", + Username: "user1", + Password: "pwd1", + }, + "shanghai_reg2": { + Protocol: "mock", + TimeoutStr: "2s", + Group: "shanghai_idc", + Address: "127.0.0.2:2181", + Username: "user1", + Password: "pwd1", + }, + "hangzhou_reg1": { + Protocol: "mock", + TimeoutStr: "2s", + Group: "hangzhou_idc", + Address: "127.0.0.3:2181", + Username: "user1", + Password: "pwd1", + }, + "hangzhou_reg2": { + Protocol: "mock", + TimeoutStr: "2s", + Group: "hangzhou_idc", + Address: "127.0.0.4:2181", + Username: "user1", + Password: "pwd1", }, }, + Protocols: map[string]*ProtocolConfig{ "mock": { Name: "mock", @@ -183,19 +140,20 @@ func doInitProviderWithSingleRegistry() { } } -func Test_Export(t *testing.T) { +func TestExport(t *testing.T) { doInitProvider() extension.SetProtocol("registry", GetProtocol) for i := range providerConfig.Services { service := providerConfig.Services[i] service.Implement(&MockService{}) + service.Protocols = providerConfig.Protocols service.Export() } providerConfig = nil } -func Test_getRandomPort(t *testing.T) { +func TestgetRandomPort(t *testing.T) { protocolConfigs := make([]*ProtocolConfig, 0, 3) ip, err := gxnet.GetLocalIP() diff --git a/config/service_discovery_config.go b/config/service_discovery_config.go new file mode 100644 index 0000000000000000000000000000000000000000..d3dc697a9b31e6949375d74aa92234093da22378 --- /dev/null +++ b/config/service_discovery_config.go @@ -0,0 +1,30 @@ +/* + * 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 + +// ServiceDiscoveryConfig will be used to create +type ServiceDiscoveryConfig struct { + // Protocol indicate which implementation will be used. + // for example, if the Protocol is nacos, it means that we will use nacosServiceDiscovery + Protocol string `yaml:"protocol" json:"protocol,omitempty"` + // Group, usually you don't need to config this field. + // you can use this to do some isolation + Group string `yaml:"group" json:"group,omitempty"` + // RemoteRef is the reference point to RemoteConfig which will be used to create remotes instances. + RemoteRef string `yaml:"remote_ref" json:"remote_ref,omitempty"` +} diff --git a/config_center/apollo/factory.go b/config_center/apollo/factory.go index f975ce13d8e5832f03051c42f92157532347d283..c52d942c4f481c64d0c465cafbd6f7f8c3bb1346 100644 --- a/config_center/apollo/factory.go +++ b/config_center/apollo/factory.go @@ -34,6 +34,7 @@ func createDynamicConfigurationFactory() config_center.DynamicConfigurationFacto type apolloConfigurationFactory struct{} +// GetDynamicConfiguration gets the dynamic configuration func (f *apolloConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, error) { dynamicConfiguration, err := newApolloConfiguration(url) if err != nil { diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go index 1cf65ed22ba0a1f765af66191ed19a04f81b0fe6..48a35093d20c445f6b6ff391f6a2e0e5b737e8f2 100644 --- a/config_center/apollo/listener.go +++ b/config_center/apollo/listener.go @@ -36,7 +36,7 @@ func NewApolloListener() *apolloListener { } } -// OnChange ... +// OnChange process each listener func (a *apolloListener) OnChange(changeEvent *agollo.ChangeEvent) { for key, change := range changeEvent.Changes { for listener := range a.listeners { diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go index de208946f1715878c2afc62fdd41df93f74797c7..8fe0a251239f7bfc6a3f70c3834da1b3af8484ba 100644 --- a/config_center/mock_dynamic_config.go +++ b/config_center/mock_dynamic_config.go @@ -33,7 +33,7 @@ import ( "github.com/apache/dubbo-go/remoting" ) -// MockDynamicConfigurationFactory ... +// MockDynamicConfigurationFactory defines content type MockDynamicConfigurationFactory struct { Content string } @@ -96,7 +96,7 @@ func (c *MockDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.Ha return gxset.NewSet(c.content), nil } -// MockDynamicConfiguration ... +// MockDynamicConfiguration uses to parse content and defines listener type MockDynamicConfiguration struct { parser parser.ConfigurationParser content string @@ -149,7 +149,7 @@ func (c *MockDynamicConfiguration) GetRule(key string, opts ...Option) (string, return c.GetProperties(key, opts...) } -// MockServiceConfigEvent ... +// MockServiceConfigEvent returns ConfiguratorConfig func (c *MockDynamicConfiguration) MockServiceConfigEvent() { config := &parser.ConfiguratorConfig{ ConfigVersion: "2.7.1", @@ -171,7 +171,7 @@ func (c *MockDynamicConfiguration) MockServiceConfigEvent() { c.listener[key].Process(&ConfigChangeEvent{Key: key, Value: string(value), ConfigType: remoting.EventTypeAdd}) } -// MockApplicationConfigEvent ... +// MockApplicationConfigEvent returns ConfiguratorConfig func (c *MockDynamicConfiguration) MockApplicationConfigEvent() { config := &parser.ConfiguratorConfig{ ConfigVersion: "2.7.1", diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index 3b432819f43327888ade3da5303e445d6a2ef0fe..6fe5c4d7df28a7693c732543140ed74f959dc77e 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -33,6 +33,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" ) @@ -89,20 +90,17 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { } url := container.GetUrl() - logDir = url.GetParam(constant.CONFIG_LOG_DIR_KEY, logDir) - + timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + if err != nil { + logger.Errorf("invalid timeout config %+v,got err %+v", + url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err) + return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) + } + nacosAddresses := strings.Split(url.Location, ",") if container.NacosClient() == nil { - //in dubbo ,every registry only connect one node ,so this is []string{r.Address} - timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) - if err != nil { - logger.Errorf("timeout config %v is invalid ,err is %v", - url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) - return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) - } - nacosAddresses := strings.Split(url.Location, ",") - newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout) + newClient, err := newNacosClient(os.nacosName, nacosAddresses, timeout, url) if err != nil { - logger.Warnf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}", + logger.Errorf("newNacosClient(name{%s}, nacos address{%v}, timeout{%d}) = error{%v}", os.nacosName, url.Location, timeout.String(), err) return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } @@ -110,41 +108,19 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { } if container.NacosClient().Client() == nil { - svrConfList := []nacosconst.ServerConfig{} - for _, nacosAddr := range container.NacosClient().NacosAddrs { - split := strings.Split(nacosAddr, ":") - port, err := strconv.ParseUint(split[1], 10, 64) - if err != nil { - logger.Warnf("nacos addr port parse error ,error message is %v", err) - continue - } - svrconf := nacosconst.ServerConfig{ - IpAddr: split[0], - Port: port, - } - svrConfList = append(svrConfList, svrconf) - } - - client, err := clients.CreateConfigClient(map[string]interface{}{ - "serverConfigs": svrConfList, - "clientConfig": nacosconst.ClientConfig{ - TimeoutMs: uint64(int32(container.NacosClient().Timeout / time.Millisecond)), - ListenInterval: 10000, - NotLoadCacheAtStart: true, - LogDir: logDir, - }, - }) - - container.NacosClient().SetClient(&client) + configClient, err := initNacosConfigClient(nacosAddresses, timeout, url) if err != nil { - logger.Errorf("nacos create config client error:%v", err) + logger.Errorf("initNacosConfigClient(addr:%+v,timeout:%v,url:%v) = err %+v", + nacosAddresses, timeout.String(), url, err) + return perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) } + container.NacosClient().SetClient(&configClient) } return perrors.WithMessagef(nil, "newNacosClient(address:%+v)", url.PrimitiveURL) } -func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*NacosClient, error) { +func newNacosClient(name string, nacosAddrs []string, timeout time.Duration, url common.URL) (*NacosClient, error) { var ( err error n *NacosClient @@ -160,12 +136,24 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*N }, } - svrConfList := make([]nacosconst.ServerConfig, 0, len(n.NacosAddrs)) - for _, nacosAddr := range n.NacosAddrs { + configClient, err := initNacosConfigClient(nacosAddrs, timeout, url) + if err != nil { + logger.Errorf("initNacosConfigClient(addr:%+v,timeout:%v,url:%v) = err %+v", + nacosAddrs, timeout.String(), url, err) + return n, perrors.WithMessagef(err, "newNacosClient(address:%+v)", url.Location) + } + n.SetClient(&configClient) + + return n, nil +} + +func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url common.URL) (config_client.IConfigClient, error) { + svrConfList := []nacosconst.ServerConfig{} + for _, nacosAddr := range nacosAddrs { split := strings.Split(nacosAddr, ":") port, err := strconv.ParseUint(split[1], 10, 64) if err != nil { - logger.Warnf("convert port , source:%s , error:%v ", split[1], err) + logger.Errorf("strconv.ParseUint(nacos addr port:%+v) = error %+v", split[1], err) continue } svrconf := nacosconst.ServerConfig{ @@ -174,21 +162,21 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration) (*N } svrConfList = append(svrConfList, svrconf) } - client, err := clients.CreateConfigClient(map[string]interface{}{ + + return clients.CreateConfigClient(map[string]interface{}{ "serverConfigs": svrConfList, "clientConfig": nacosconst.ClientConfig{ - TimeoutMs: uint64(timeout / time.Millisecond), - ListenInterval: 20000, + TimeoutMs: uint64(int32(timeout / time.Millisecond)), + ListenInterval: uint64(int32(timeout / time.Millisecond)), NotLoadCacheAtStart: true, - LogDir: logDir, + LogDir: url.GetParam(constant.NACOS_LOG_DIR_KEY, logDir), + CacheDir: url.GetParam(constant.NACOS_CACHE_DIR_KEY, ""), + Endpoint: url.GetParam(constant.NACOS_ENDPOINT, ""), + Username: url.GetParam(constant.NACOS_USERNAME, ""), + Password: url.GetParam(constant.NACOS_PASSWORD, ""), + NamespaceId: url.GetParam(constant.NACOS_NAMESPACE_ID, ""), }, }) - n.SetClient(&client) - if err != nil { - return nil, perrors.WithMessagef(err, "nacos clients.CreateConfigClient(nacosAddrs:%+v)", nacosAddrs) - } - - return n, nil } // Done Get nacos client exit signal @@ -233,5 +221,4 @@ func (n *NacosClient) Close() { n.stop() n.SetClient(nil) - logger.Warnf("nacosClient{name:%s, nacos addr:%s} exit now.", n.name, n.NacosAddrs) } diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go index 2e7f4645149b8cd70510713d49ced9ba31d66bb6..01319f362b956a0a15be8d5d4dde2a8b2be57c89 100644 --- a/config_center/nacos/client_test.go +++ b/config_center/nacos/client_test.go @@ -56,7 +56,7 @@ func TestNewNacosClient(t *testing.T) { func TestSetNacosClient(t *testing.T) { server := mockCommonNacosServer() - nacosURL := server.Listener.Addr().String() + nacosURL := "registry://" + server.Listener.Addr().String() registryUrl, _ := common.NewURL(nacosURL) c := &nacosDynamicConfiguration{ url: ®istryUrl, @@ -66,7 +66,7 @@ func TestSetNacosClient(t *testing.T) { client = &NacosClient{ name: nacosClientName, NacosAddrs: []string{nacosURL}, - Timeout: 15, + Timeout: 15 * time.Second, exit: make(chan struct{}), onceClose: func() { close(client.exit) diff --git a/config_center/nacos/impl.go b/config_center/nacos/impl.go index 007b8be142274b63ceb56dd00399cdaf29c3746d..bbf707b93811663d0a259c6704e1008bfa91c5c1 100644 --- a/config_center/nacos/impl.go +++ b/config_center/nacos/impl.go @@ -36,8 +36,16 @@ import ( "github.com/apache/dubbo-go/config_center/parser" ) -const nacosClientName = "nacos config_center" +const ( + nacosClientName = "nacos config_center" + // the number is a little big tricky + // it will be used in query which looks up all keys with the target group + // now, one key represents one application + // so only a group has more than 9999 applications will failed + maxKeysNum = 9999 +) +// nacosDynamicConfiguration is the implementation of DynamicConfiguration based on nacos type nacosDynamicConfiguration struct { url *common.URL rootPath string @@ -108,9 +116,23 @@ func (n *nacosDynamicConfiguration) PublishConfig(key string, group string, valu // GetConfigKeysByGroup will return all keys with the group func (n *nacosDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) { - // TODO (the golang client of nacos does not support batch API) - // we should build a issue and then think about how to resolve this problem - return nil, perrors.New("unsupport operation, wait for implement") + group = n.resolvedGroup(group) + page, err := (*n.client.Client()).SearchConfig(vo.SearchConfigParm{ + Search: "accurate", + Group: group, + PageNo: 1, + // actually it's impossible for user to create 9999 application under one group + PageSize: maxKeysNum, + }) + + result := gxset.NewSet() + if err != nil { + return result, perrors.WithMessage(err, "can not find the client config") + } + for _, itm := range page.PageItems { + result.Add(itm.DataId) + } + return result, nil } // GetRule Get router rule diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go index 453fa11f955c83ae2925a959e7a2465a2a0e7796..88d200edc927c62967975dff28e19c03743125f0 100644 --- a/config_center/nacos/impl_test.go +++ b/config_center/nacos/impl_test.go @@ -89,6 +89,34 @@ func TestGetConfig(t *testing.T) { assert.NoError(t, err) } +func TestNacosDynamicConfiguration_GetConfigKeysByGroup(t *testing.T) { + data := ` +{ + "PageItems": [ + { + "dataId": "application" + } + ] +} +` + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(data)) + })) + + nacosURL := strings.ReplaceAll(ts.URL, "http", "registry") + regurl, _ := common.NewURL(nacosURL) + nacosConfiguration, err := newNacosDynamicConfiguration(®url) + assert.NoError(t, err) + + nacosConfiguration.SetParser(&parser.DefaultConfigurationParser{}) + + configs, err := nacosConfiguration.GetConfigKeysByGroup("dubbo") + assert.Nil(t, err) + assert.Equal(t, 1, configs.Size()) + assert.True(t, configs.Contains("application")) + +} + func TestNacosDynamicConfigurationPublishConfig(t *testing.T) { nacos, err := initNacosData(t) assert.Nil(t, err) @@ -105,8 +133,6 @@ func TestAddListener(t *testing.T) { listener := &mockDataListener{} time.Sleep(time.Second * 2) nacos.AddListener("dubbo.properties", listener) - listener.wg.Add(1) - listener.wg.Wait() } func TestRemoveListener(_ *testing.T) { diff --git a/config_center/nacos/listener.go b/config_center/nacos/listener.go index 4c995389d38e1a39670aff26025f030bd4bfb1ec..3118a9d0529bf8762dc7ee864af32b044b74fd49 100644 --- a/config_center/nacos/listener.go +++ b/config_center/nacos/listener.go @@ -46,7 +46,9 @@ func (n *nacosDynamicConfiguration) addListener(key string, listener config_cent go callback(listener, namespace, group, dataId, data) }, }) - logger.Errorf("nacos : listen config fail, error:%v ", err) + if err != nil { + logger.Errorf("nacos : listen config fail, error:%v ", err) + } newListener := make(map[config_center.ConfigurationListener]context.CancelFunc) newListener[listener] = cancel n.keyListeners.Store(key, newListener) diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index 6fbdc27d4339150bfec624f7dc5ea0f6a608d7a7..f794221f9cf3c689d52b32a57ab51f7908f17297 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -35,13 +35,13 @@ import ( ) const ( - // ScopeApplication ... + // ScopeApplication : scope application ScopeApplication = "application" - // GeneralType ... + // GeneralType defines the general type GeneralType = "general" ) -// ConfigurationParser ... +// ConfigurationParser interface type ConfigurationParser interface { Parse(string) (map[string]string, error) ParseToUrls(content string) ([]*common.URL, error) @@ -50,7 +50,7 @@ type ConfigurationParser interface { // DefaultConfigurationParser for supporting properties file in config center type DefaultConfigurationParser struct{} -// ConfiguratorConfig ... +// ConfiguratorConfig defines configurator config type ConfiguratorConfig struct { ConfigVersion string `yaml:"configVersion"` Scope string `yaml:"scope"` @@ -59,7 +59,7 @@ type ConfiguratorConfig struct { Configs []ConfigItem `yaml:"configs"` } -// ConfigItem ... +// ConfigItem defines config item type ConfigItem struct { Type string `yaml:"type"` Enabled bool `yaml:"enabled"` @@ -81,7 +81,7 @@ func (parser *DefaultConfigurationParser) Parse(content string) (map[string]stri return pps.Map(), nil } -// ParseToUrls ... +// ParseToUrls is used to parse content to urls func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common.URL, error) { config := ConfiguratorConfig{} if err := yaml.Unmarshal([]byte(content), &config); err != nil { @@ -110,6 +110,7 @@ func (parser *DefaultConfigurationParser) ParseToUrls(content string) ([]*common return allUrls, nil } +// serviceItemToUrls is used to transfer item and config to urls func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -156,6 +157,7 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR return urls, nil } +// nolint func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, error) { var addresses = item.Addresses if len(addresses) == 0 { @@ -196,6 +198,7 @@ func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, e return urls, nil } +// getServiceString returns service string func getServiceString(service string) (string, error) { if len(service) == 0 { return "", perrors.New("service field in configuration is null.") @@ -219,6 +222,7 @@ func getServiceString(service string) (string, error) { return serviceStr, nil } +// nolint func getParamString(item ConfigItem) (string, error) { var retStr string retStr = retStr + "category=" @@ -241,6 +245,7 @@ func getParamString(item ConfigItem) (string, error) { return retStr, nil } +// getEnabledString returns enabled string func getEnabledString(item ConfigItem, config ConfiguratorConfig) string { retStr := "&enabled=" if len(item.Type) == 0 || item.Type == GeneralType { diff --git a/config_center/zookeeper/listener.go b/config_center/zookeeper/listener.go index 747c4be352add3f549eaf02e83da6442a8a84c6a..bc6eb6d6eec4413515228a712cd11810e11f08cc 100644 --- a/config_center/zookeeper/listener.go +++ b/config_center/zookeeper/listener.go @@ -27,7 +27,7 @@ import ( "github.com/apache/dubbo-go/remoting" ) -// CacheListener ... +// CacheListener defines keyListeners and rootPath type CacheListener struct { keyListeners sync.Map rootPath string diff --git a/filter/filter.go b/filter/filter.go index d20ca72c345c6812f4bce6df5dbaf683429a9874..804bf3b9df8040c6377648f150c25d421b29b93d 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -27,7 +27,7 @@ import ( // Filter interface defines the functions of a filter // Extension - Filter type Filter interface { - // Invoke is the core function of a filter, it determins the process of the filter + // Invoke is the core function of a filter, it determines the process of the filter Invoke(context.Context, protocol.Invoker, protocol.Invocation) protocol.Result // OnResponse updates the results from Invoke and then returns the modified results. OnResponse(context.Context, protocol.Result, protocol.Invoker, protocol.Invocation) protocol.Result diff --git a/filter/filter_impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go index 49cdc2287c28ae0cbbd0fcab3700536595bb0f5e..621012c24c0ad12b3bda397148a3ed9c29d080ed 100644 --- a/filter/filter_impl/access_log_filter.go +++ b/filter/filter_impl/access_log_filter.go @@ -36,20 +36,20 @@ import ( const ( //used in URL. - // FileDateFormat ... + // nolint FileDateFormat = "2006-01-02" - // MessageDateLayout ... + // nolint MessageDateLayout = "2006-01-02 15:04:05" - // LogMaxBuffer ... + // nolint LogMaxBuffer = 5000 - // LogFileMode ... + // nolint LogFileMode = 0600 // those fields are the data collected by this filter - // Types ... + // nolint Types = "types" - // Arguments ... + // nolint Arguments = "arguments" ) diff --git a/filter/filter_impl/access_log_filter_test.go b/filter/filter_impl/access_log_filter_test.go index f0de24d2a89f35876a32763eeb75495e8919ecd9..55c328cc30ae892c603fcc65034e48d2a52403d2 100644 --- a/filter/filter_impl/access_log_filter_test.go +++ b/filter/filter_impl/access_log_filter_test.go @@ -53,7 +53,7 @@ func TestAccessLogFilter_Invoke_Not_Config(t *testing.T) { assert.Nil(t, result.Error()) } -func TestAccessLogFilter_Invoke_Default_Config(t *testing.T) { +func TestAccessLogFilterInvokeDefaultConfig(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() url, _ := common.NewURL( @@ -74,7 +74,7 @@ func TestAccessLogFilter_Invoke_Default_Config(t *testing.T) { assert.Nil(t, result.Error()) } -func TestAccessLogFilter_OnResponse(t *testing.T) { +func TestAccessLogFilterOnResponse(t *testing.T) { result := &protocol.RPCResult{} accessLogFilter := GetAccessLogFilter() response := accessLogFilter.OnResponse(nil, result, nil, nil) diff --git a/filter/filter_impl/active_filter_test.go b/filter/filter_impl/active_filter_test.go index d5a51d50d93bd9769001964fbb0ae7905eb24980..6b72830e6a1a523b775b9294863ab18f8fe518a2 100644 --- a/filter/filter_impl/active_filter_test.go +++ b/filter/filter_impl/active_filter_test.go @@ -36,7 +36,7 @@ import ( "github.com/apache/dubbo-go/protocol/mock" ) -func TestActiveFilter_Invoke(t *testing.T) { +func TestActiveFilterInvoke(t *testing.T) { invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]string, 0)) url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") filter := ActiveFilter{} @@ -50,7 +50,7 @@ func TestActiveFilter_Invoke(t *testing.T) { } -func TestActiveFilter_OnResponse(t *testing.T) { +func TestActiveFilterOnResponse(t *testing.T) { c := protocol.CurrentTimeMillis() elapsed := 100 invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ diff --git a/filter/filter_impl/echo_filter.go b/filter/filter_impl/echo_filter.go index 7da5ec7029ea698b1bf1a14ad36123fbec3aacf7..24ba6e47acbffd28eedfffcd0dbaf9717e222946 100644 --- a/filter/filter_impl/echo_filter.go +++ b/filter/filter_impl/echo_filter.go @@ -65,7 +65,7 @@ func (ef *EchoFilter) OnResponse(_ context.Context, result protocol.Result, _ pr return result } -// GetFilter ... +// GetFilter gets the Filter func GetFilter() filter.Filter { return &EchoFilter{} } diff --git a/filter/filter_impl/echo_filter_test.go b/filter/filter_impl/echo_filter_test.go index fc09bdce696c6be3c9e11d0ac864b187d1d85cde..b821a1a30cb8af7e957fac45beecd46067627f91 100644 --- a/filter/filter_impl/echo_filter_test.go +++ b/filter/filter_impl/echo_filter_test.go @@ -32,7 +32,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestEchoFilter_Invoke(t *testing.T) { +func TestEchoFilterInvoke(t *testing.T) { filter := GetFilter() result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(common.URL{}), invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, nil)) assert.Equal(t, "OK", result.Result()) diff --git a/filter/filter_impl/execute_limit_filter.go b/filter/filter_impl/execute_limit_filter.go index bfc5096ca089867f6e6234089e387d3f9b48a3aa..5fc309cfb49fbeef5e933a84c1b80654087da701 100644 --- a/filter/filter_impl/execute_limit_filter.go +++ b/filter/filter_impl/execute_limit_filter.go @@ -74,7 +74,7 @@ type ExecuteLimitFilter struct { executeState *concurrent.Map } -// ExecuteState ... +// ExecuteState defines the concurrent count type ExecuteState struct { concurrentCount int64 } diff --git a/filter/filter_impl/execute_limit_filter_test.go b/filter/filter_impl/execute_limit_filter_test.go index ae8641f2db0b98b59f9939cfc85f3ad096b1bc7f..d36d6edef1ec52c24a9ccd64233b4620b4f10bc7 100644 --- a/filter/filter_impl/execute_limit_filter_test.go +++ b/filter/filter_impl/execute_limit_filter_test.go @@ -34,7 +34,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestExecuteLimitFilter_Invoke_Ignored(t *testing.T) { +func TestExecuteLimitFilterInvokeIgnored(t *testing.T) { methodName := "hello" invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) @@ -49,7 +49,7 @@ func TestExecuteLimitFilter_Invoke_Ignored(t *testing.T) { assert.Nil(t, result.Error()) } -func TestExecuteLimitFilter_Invoke_Configure_Error(t *testing.T) { +func TestExecuteLimitFilterInvokeConfigureError(t *testing.T) { methodName := "hello1" invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) @@ -66,7 +66,7 @@ func TestExecuteLimitFilter_Invoke_Configure_Error(t *testing.T) { assert.Nil(t, result.Error()) } -func TestExecuteLimitFilter_Invoke(t *testing.T) { +func TestExecuteLimitFilterInvoke(t *testing.T) { methodName := "hello1" invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go index 3f4d714e6b0cbdf48f5e1afce3222a18857041f9..d385054ed98518177aea5e574e034cb35c72e398 100644 --- a/filter/filter_impl/generic_filter.go +++ b/filter/filter_impl/generic_filter.go @@ -47,7 +47,7 @@ func init() { // when do a generic invoke, struct need to be map -// GenericFilter ... +// nolint type GenericFilter struct{} // Invoke turns the parameters to map for generic method diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go index b08229199898a30657682d47c32689dc084f5bf4..e40733209b2e1db972ab576dea54f206a1e888c0 100644 --- a/filter/filter_impl/generic_filter_test.go +++ b/filter/filter_impl/generic_filter_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_struct2MapAll(t *testing.T) { +func TestStruct2MapAll(t *testing.T) { var testData struct { AaAa string `m:"aaAa"` BaBa string @@ -64,7 +64,7 @@ type testStruct struct { } `m:"xxYy"` } -func Test_struct2MapAll_Slice(t *testing.T) { +func TestStruct2MapAllSlice(t *testing.T) { var testData struct { AaAa string `m:"aaAa"` BaBa string @@ -89,7 +89,7 @@ func Test_struct2MapAll_Slice(t *testing.T) { assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].([]interface{})[0].(map[string]interface{})["xxYy"]).Kind()) } -func Test_struct2MapAll_Map(t *testing.T) { +func TestStruct2MapAllMap(t *testing.T) { var testData struct { AaAa string Baba map[string]interface{} diff --git a/filter/filter_impl/generic_service_filter.go b/filter/filter_impl/generic_service_filter.go index 6272df6b39b0c18a77721f3a8c9e92618133aa6c..3711e68cce67dacfec074a5e44c080a629bce305 100644 --- a/filter/filter_impl/generic_service_filter.go +++ b/filter/filter_impl/generic_service_filter.go @@ -40,9 +40,9 @@ import ( ) const ( - // GENERIC_SERVICE ... + // GENERIC_SERVICE defines the filter name GENERIC_SERVICE = "generic_service" - // GENERIC_SERIALIZATION_DEFAULT ... + // nolint GENERIC_SERIALIZATION_DEFAULT = "true" ) @@ -50,10 +50,10 @@ func init() { extension.SetFilter(GENERIC_SERVICE, GetGenericServiceFilter) } -// GenericServiceFilter ... +// nolint type GenericServiceFilter struct{} -// Invoke ... +// Invoke is used to call service method by invocation func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking generic service filter.") logger.Debugf("generic service filter methodName:%v,args:%v", invocation.MethodName(), len(invocation.Arguments())) @@ -115,7 +115,7 @@ func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Inv return invoker.Invoke(ctx, newInvocation) } -// OnResponse ... +// nolint func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 && result.Result() != nil { v := reflect.ValueOf(result.Result()) @@ -127,7 +127,7 @@ func (ef *GenericServiceFilter) OnResponse(ctx context.Context, result protocol. return result } -// GetGenericServiceFilter ... +// nolint func GetGenericServiceFilter() filter.Filter { return &GenericServiceFilter{} } diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go index 2a911659f068b53836b87af5caf5773d8ac5f119..67819717cf07383373f905ed9420a42cc0bfddb9 100644 --- a/filter/filter_impl/generic_service_filter_test.go +++ b/filter/filter_impl/generic_service_filter_test.go @@ -51,7 +51,7 @@ func (c *TestStruct) JavaClassName() string { type TestService struct{} -// MethodOne ... +// nolint func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []TestStruct, test3 interface{}, test4 []interface{}, test5 *string) (*TestStruct, error) { if test1 == nil { @@ -72,12 +72,12 @@ func (ts *TestService) MethodOne(_ context.Context, test1 *TestStruct, test2 []T return &TestStruct{}, nil } -// Reference ... +// nolint func (*TestService) Reference() string { return "com.test.Path" } -func TestGenericServiceFilter_Invoke(t *testing.T) { +func TestGenericServiceFilterInvoke(t *testing.T) { hessian.RegisterPOJO(&TestStruct{}) methodName := "$invoke" m := make(map[string]interface{}) @@ -105,7 +105,7 @@ func TestGenericServiceFilter_Invoke(t *testing.T) { assert.Nil(t, result.Error()) } -func TestGenericServiceFilter_ResponseTestStruct(t *testing.T) { +func TestGenericServiceFilterResponseTestStruct(t *testing.T) { ts := &TestStruct{ AaAa: "aaa", BaBa: "bbb", @@ -130,7 +130,7 @@ func TestGenericServiceFilter_ResponseTestStruct(t *testing.T) { assert.Equal(t, reflect.ValueOf(r.Result()).Kind(), reflect.Map) } -func TestGenericServiceFilter_ResponseString(t *testing.T) { +func TestGenericServiceFilterResponseString(t *testing.T) { str := "111" result := &protocol.RPCResult{ Rest: str, diff --git a/filter/filter_impl/graceful_shutdown_filter_test.go b/filter/filter_impl/graceful_shutdown_filter_test.go index 4c670933e3dcec29ad9ae7bfef250b4236ae7c54..87ac2eac616a20617b7a5e68254a1f47ecb8ac17 100644 --- a/filter/filter_impl/graceful_shutdown_filter_test.go +++ b/filter/filter_impl/graceful_shutdown_filter_test.go @@ -38,7 +38,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestGenericFilter_Invoke(t *testing.T) { +func TestGenericFilterInvoke(t *testing.T) { invoc := invocation.NewRPCInvocation("GetUser", []interface{}{"OK"}, make(map[string]string, 0)) invokeUrl := common.NewURLWithOptions( diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index 711ef71c44192c5a1d76783a3b3d4cbd0b97632c..e2275149f1229c87ed4ae3f89dc9ae32d9fe6461 100644 --- a/filter/filter_impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -37,11 +37,11 @@ import ( ) const ( - // HYSTRIX_CONSUMER ... + // nolint HYSTRIX_CONSUMER = "hystrix_consumer" - // HYSTRIX_PROVIDER ... + // nolint HYSTRIX_PROVIDER = "hystrix_provider" - // HYSTRIX ... + // nolint HYSTRIX = "hystrix" ) @@ -85,14 +85,14 @@ func NewHystrixFilterError(err error, failByHystrix bool) error { } } -// HystrixFilter ... +// nolint type HystrixFilter struct { COrP bool //true for consumer res map[string][]*regexp.Regexp ifNewMap sync.Map } -// Invoke is an implentation of filter, provides Hystrix pattern latency and fault tolerance +// Invoke is an implementation of filter, provides Hystrix pattern latency and fault tolerance func (hf *HystrixFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { cmdName := fmt.Sprintf("%s&method=%s", invoker.GetUrl().Key(), invocation.MethodName()) @@ -256,7 +256,7 @@ func initHystrixConfigProvider() error { // return initHystrixConfig() //} -// CommandConfigWithError ... +// nolint type CommandConfigWithError struct { Timeout int `yaml:"timeout"` MaxConcurrentRequests int `yaml:"max_concurrent_requests"` @@ -274,14 +274,14 @@ type CommandConfigWithError struct { //- ErrorPercentThreshold: it causes circuits to open once the rolling measure of errors exceeds this percent of requests //See hystrix doc -// HystrixFilterConfig ... +// nolint type HystrixFilterConfig struct { Configs map[string]*CommandConfigWithError Default string Services map[string]ServiceHystrixConfig } -// ServiceHystrixConfig ... +// nolint type ServiceHystrixConfig struct { ServiceConfig string `yaml:"service_config"` Methods map[string]string diff --git a/filter/filter_impl/hystrix_filter_test.go b/filter/filter_impl/hystrix_filter_test.go index 71fc097c8bf4752e0cb2b451b0da7e16480b0701..eebbae55565aa7072846d368403605002df0c61b 100644 --- a/filter/filter_impl/hystrix_filter_test.go +++ b/filter/filter_impl/hystrix_filter_test.go @@ -90,7 +90,7 @@ func TestGetHystrixFilter(t *testing.T) { assert.NotNil(t, filterGot) } -func TestGetConfig_1(t *testing.T) { +func TestGetConfig1(t *testing.T) { mockInitHystrixConfig() configGot := getConfig("com.ikurento.user.UserProvider", "GetUser", true) assert.NotNil(t, configGot) @@ -101,7 +101,7 @@ func TestGetConfig_1(t *testing.T) { assert.Equal(t, 5, configGot.RequestVolumeThreshold) } -func TestGetConfig_2(t *testing.T) { +func TestGetConfig2(t *testing.T) { mockInitHystrixConfig() configGot := getConfig("com.ikurento.user.UserProvider", "GetUser0", true) assert.NotNil(t, configGot) @@ -112,7 +112,7 @@ func TestGetConfig_2(t *testing.T) { assert.Equal(t, 15, configGot.RequestVolumeThreshold) } -func TestGetConfig_3(t *testing.T) { +func TestGetConfig3(t *testing.T) { mockInitHystrixConfig() //This should use default configGot := getConfig("Mock.Service", "GetMock", true) @@ -145,7 +145,7 @@ func (iv *testMockFailInvoker) Invoke(_ context.Context, _ protocol.Invocation) } } -func TestHystrixFilter_Invoke_Success(t *testing.T) { +func TestHystrixFilterInvokeSuccess(t *testing.T) { hf := &HystrixFilter{} result := hf.Invoke(context.Background(), &testMockSuccessInvoker{}, &invocation.RPCInvocation{}) assert.NotNil(t, result) @@ -153,14 +153,14 @@ func TestHystrixFilter_Invoke_Success(t *testing.T) { assert.NotNil(t, result.Result()) } -func TestHystrixFilter_Invoke_Fail(t *testing.T) { +func TestHystrixFilterInvokeFail(t *testing.T) { hf := &HystrixFilter{} result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{}) assert.NotNil(t, result) assert.Error(t, result.Error()) } -func TestHystricFilter_Invoke_CircuitBreak(t *testing.T) { +func TestHystricFilterInvokeCircuitBreak(t *testing.T) { mockInitHystrixConfig() hystrix.Flush() hf := &HystrixFilter{COrP: true} @@ -183,7 +183,7 @@ func TestHystricFilter_Invoke_CircuitBreak(t *testing.T) { } -func TestHystricFilter_Invoke_CircuitBreak_Omit_Exception(t *testing.T) { +func TestHystricFilterInvokeCircuitBreakOmitException(t *testing.T) { mockInitHystrixConfig() hystrix.Flush() reg, _ := regexp.Compile(".*exception.*") diff --git a/filter/filter_impl/metrics_filter_test.go b/filter/filter_impl/metrics_filter_test.go index 709404a2af4f4df0dbf625dbbbd673e34975c0db..881106f4bc5a7890569be347122da5144e440c8b 100644 --- a/filter/filter_impl/metrics_filter_test.go +++ b/filter/filter_impl/metrics_filter_test.go @@ -38,7 +38,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestMetricsFilter_Invoke(t *testing.T) { +func TestMetricsFilterInvoke(t *testing.T) { // prepare the mock reporter config.GetMetricConfig().Reporters = []string{"mock"} diff --git a/filter/filter_impl/seata_filter.go b/filter/filter_impl/seata_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..7722d2954f905ece4a1b48628c31c06debf45614 --- /dev/null +++ b/filter/filter_impl/seata_filter.go @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter_impl + +import ( + "context" + "strings" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +const ( + SEATA = "seata" + SEATA_XID = "SEATA_XID" +) + +func init() { + extension.SetFilter(SEATA, getSeataFilter) +} + +// SeataFilter when use seata-golang, use this filter to transfer xid +type SeataFilter struct{} + +// When use Seata, transfer xid by attachments +// Invoke Get Xid by attachment key `SEATA_XID` +func (sf *SeataFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + logger.Infof("invoking seata filter.") + xid := invocation.AttachmentsByKey(SEATA_XID, "") + if strings.TrimSpace(xid) != "" { + logger.Debugf("Method: %v,Xid: %v", invocation.MethodName(), xid) + return invoker.Invoke(context.WithValue(ctx, SEATA_XID, xid), invocation) + } + return invoker.Invoke(ctx, invocation) +} + +// OnResponse dummy process, returns the result directly +func (sf *SeataFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + return result +} + +// getSeataFilter create SeataFilter instance +func getSeataFilter() filter.Filter { + return &SeataFilter{} +} diff --git a/filter/filter_impl/seata_filter_test.go b/filter/filter_impl/seata_filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6c39897f7aa3a044490e9eece8f352a9db9fc74b --- /dev/null +++ b/filter/filter_impl/seata_filter_test.go @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter_impl + +import ( + "context" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +type testMockSeataInvoker struct { + protocol.BaseInvoker +} + +func (iv *testMockSeataInvoker) Invoke(ctx context.Context, _ protocol.Invocation) protocol.Result { + val := ctx.Value(SEATA_XID) + if val != nil { + xid, ok := val.(string) + if ok { + return &protocol.RPCResult{Rest: xid} + } + } + return &protocol.RPCResult{} +} + +func TestSeataFilter_Invoke(t *testing.T) { + filter := getSeataFilter() + result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, map[string]string{ + SEATA_XID: "10.30.21.227:8091:2000047792", + })) + assert.Equal(t, "10.30.21.227:8091:2000047792", result.Result()) +} diff --git a/filter/filter_impl/token_filter.go b/filter/filter_impl/token_filter.go index 23742c66e94d9ecfc09d004441a54aad86ef049e..fe4e38747ed10a40950c87747220339d0d566781 100644 --- a/filter/filter_impl/token_filter.go +++ b/filter/filter_impl/token_filter.go @@ -34,7 +34,7 @@ import ( ) const ( - // TOKEN ... + // nolint TOKEN = "token" ) @@ -66,7 +66,7 @@ func (tf *TokenFilter) OnResponse(ctx context.Context, result protocol.Result, i return result } -// GetTokenFilter ... +// nolint func GetTokenFilter() filter.Filter { return &TokenFilter{} } diff --git a/filter/filter_impl/token_filter_test.go b/filter/filter_impl/token_filter_test.go index b8b297e67267640a1c294541afdd4e062bfebb25..c2f69bd03941b1404585dc5842c56eb2bf3c918f 100644 --- a/filter/filter_impl/token_filter_test.go +++ b/filter/filter_impl/token_filter_test.go @@ -34,7 +34,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestTokenFilter_Invoke(t *testing.T) { +func TestTokenFilterInvoke(t *testing.T) { filter := GetTokenFilter() url := common.NewURLWithOptions( @@ -50,7 +50,7 @@ func TestTokenFilter_Invoke(t *testing.T) { assert.Nil(t, result.Result()) } -func TestTokenFilter_InvokeEmptyToken(t *testing.T) { +func TestTokenFilterInvokeEmptyToken(t *testing.T) { filter := GetTokenFilter() testUrl := common.URL{} @@ -61,7 +61,7 @@ func TestTokenFilter_InvokeEmptyToken(t *testing.T) { assert.Nil(t, result.Result()) } -func TestTokenFilter_InvokeEmptyAttach(t *testing.T) { +func TestTokenFilterInvokeEmptyAttach(t *testing.T) { filter := GetTokenFilter() testUrl := common.NewURLWithOptions( @@ -72,7 +72,7 @@ func TestTokenFilter_InvokeEmptyAttach(t *testing.T) { assert.NotNil(t, result.Error()) } -func TestTokenFilter_InvokeNotEqual(t *testing.T) { +func TestTokenFilterInvokeNotEqual(t *testing.T) { filter := GetTokenFilter() testUrl := common.NewURLWithOptions( diff --git a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go index 7419a4576122d4db334969b0711666b5b2816e60..d495e035de521612cc9b85ec9e817ae3355a818b 100644 --- a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go @@ -29,7 +29,7 @@ import ( ) const ( - // FixedWindowKey ... + // FixedWindowKey defines tps limit algorithm FixedWindowKey = "fixedWindow" ) diff --git a/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go index 5eaf2f707dcc9dd6cf325988242623dd5161c1a8..83a2f9b54d2a7b3d603538201a822fa0428523f5 100644 --- a/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go +++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFixedWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) { +func TestFixedWindowTpsLimitStrategyImplIsAllowable(t *testing.T) { creator := &fixedWindowStrategyCreator{} strategy := creator.Create(2, 60000) assert.True(t, strategy.IsAllowable()) diff --git a/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go index 57342d1c443993c49c6124f0ef28dae5ebb203e8..ca5d70ae143eb0d08ba1f3407a7ea10d9c113643 100644 --- a/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go +++ b/filter/filter_impl/tps/tps_limit_sliding_window_strategy_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSlidingWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) { +func TestSlidingWindowTpsLimitStrategyImplIsAllowable(t *testing.T) { creator := &slidingWindowStrategyCreator{} strategy := creator.Create(2, 60000) assert.True(t, strategy.IsAllowable()) diff --git a/filter/filter_impl/tps/tps_limit_strategy_mock.go b/filter/filter_impl/tps/tps_limit_strategy_mock.go index c228c7349ce6ad305051e0ba9b26dddee8c12c71..be76466092b48022330a6fb35ba99bbf0d03134e 100644 --- a/filter/filter_impl/tps/tps_limit_strategy_mock.go +++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go @@ -23,6 +23,9 @@ package tps import ( gomock "github.com/golang/mock/gomock" +) + +import ( reflect "reflect" ) diff --git a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go index 90cd15201cd71aafcc50a1dfb801ece7a5dee26a..80d31f8ff053d4d86b5f565e7018294b049f2801 100644 --- a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go +++ b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestThreadSafeFixedWindowTpsLimitStrategyImpl_IsAllowable(t *testing.T) { +func TestThreadSafeFixedWindowTpsLimitStrategyImplIsAllowable(t *testing.T) { creator := &threadSafeFixedWindowStrategyCreator{} strategy := creator.Create(2, 60000) assert.True(t, strategy.IsAllowable()) diff --git a/filter/filter_impl/tps/tps_limiter_method_service_test.go b/filter/filter_impl/tps/tps_limiter_method_service_test.go index 441224a3e35147b85c3553871dcaa1fefd09db04..edae99ec2d3157ad7f0d81c95a2fb181410475fa 100644 --- a/filter/filter_impl/tps/tps_limiter_method_service_test.go +++ b/filter/filter_impl/tps/tps_limiter_method_service_test.go @@ -34,7 +34,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestMethodServiceTpsLimiterImpl_IsAllowable_Only_Service_Level(t *testing.T) { +func TestMethodServiceTpsLimiterImplIsAllowableOnlyServiceLevel(t *testing.T) { methodName := "hello" invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) @@ -61,7 +61,7 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_Only_Service_Level(t *testing.T assert.True(t, result) } -func TestMethodServiceTpsLimiterImpl_IsAllowable_No_Config(t *testing.T) { +func TestMethodServiceTpsLimiterImplIsAllowableNoConfig(t *testing.T) { methodName := "hello1" invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) // ctrl := gomock.NewController(t) @@ -77,7 +77,7 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_No_Config(t *testing.T) { assert.True(t, result) } -func TestMethodServiceTpsLimiterImpl_IsAllowable_Method_Level_Override(t *testing.T) { +func TestMethodServiceTpsLimiterImplIsAllowableMethodLevelOverride(t *testing.T) { methodName := "hello2" methodConfigPrefix := "methods." + methodName + "." invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) @@ -110,7 +110,7 @@ func TestMethodServiceTpsLimiterImpl_IsAllowable_Method_Level_Override(t *testin assert.True(t, result) } -func TestMethodServiceTpsLimiterImpl_IsAllowable_Both_Method_And_Service(t *testing.T) { +func TestMethodServiceTpsLimiterImplIsAllowableBothMethodAndService(t *testing.T) { methodName := "hello3" methodConfigPrefix := "methods." + methodName + "." invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) diff --git a/filter/filter_impl/tps_limit_filter_test.go b/filter/filter_impl/tps_limit_filter_test.go index cc423ae1e5f3589dd60b0c8655f1123c290f0ffc..274e4e6de61b94079e9ad3b2f7a5bcd79a276cc6 100644 --- a/filter/filter_impl/tps_limit_filter_test.go +++ b/filter/filter_impl/tps_limit_filter_test.go @@ -39,7 +39,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestTpsLimitFilter_Invoke_With_No_TpsLimiter(t *testing.T) { +func TestTpsLimitFilterInvokeWithNoTpsLimiter(t *testing.T) { tpsFilter := GetTpsLimitFilter() invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), @@ -55,7 +55,7 @@ func TestTpsLimitFilter_Invoke_With_No_TpsLimiter(t *testing.T) { } -func TestGenericFilter_Invoke_With_Default_TpsLimiter(t *testing.T) { +func TestGenericFilterInvokeWithDefaultTpsLimiter(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockLimiter := tps.NewMockTpsLimiter(ctrl) @@ -78,7 +78,7 @@ func TestGenericFilter_Invoke_With_Default_TpsLimiter(t *testing.T) { assert.Nil(t, result.Result()) } -func TestGenericFilter_Invoke_With_Default_TpsLimiter_Not_Allow(t *testing.T) { +func TestGenericFilterInvokeWithDefaultTpsLimiterNotAllow(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockLimiter := tps.NewMockTpsLimiter(ctrl) diff --git a/filter/filter_impl/tracing_filter_test.go b/filter/filter_impl/tracing_filter_test.go index a51692dddcc3400032650f4953eb1e28fb047709..57f4095d49be784d7688d2acf17c1ea0225d0000 100644 --- a/filter/filter_impl/tracing_filter_test.go +++ b/filter/filter_impl/tracing_filter_test.go @@ -26,17 +26,14 @@ import ( "github.com/opentracing/opentracing-go" ) -import ( - "github.com/apache/dubbo-go/common/constant" -) - import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) -func TestTracingFilter_Invoke(t *testing.T) { +func TestTracingFilterInvoke(t *testing.T) { url, _ := common.NewURL( "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider" + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser." + diff --git a/go.mod b/go.mod index 7e4b8d9a4a000e6ee6b138fd57a4df653d416b2f..44f73d342e059cce6b11c87efc3549a46a074178 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,7 @@ module github.com/apache/dubbo-go 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.6.1-0.20200623062814-707fde850279 - github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect + github.com/apache/dubbo-go-hessian2 v1.6.1 github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/etcd v3.3.13+incompatible github.com/coreos/go-semver v0.3.0 // indirect @@ -13,11 +11,10 @@ require ( github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/creasty/defaults v1.3.0 github.com/dubbogo/getty v1.3.7 - github.com/dubbogo/go-zookeeper v1.0.0 + github.com/dubbogo/go-zookeeper v1.0.1 github.com/dubbogo/gost v1.9.0 github.com/emicklei/go-restful/v3 v3.0.0 - github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect - github.com/go-errors/errors v1.0.1 // indirect + github.com/go-co-op/gocron v0.1.1 github.com/go-resty/resty/v2 v2.1.0 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/mock v1.3.1 @@ -29,34 +26,29 @@ require ( github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/hashicorp/consul v1.5.3 github.com/hashicorp/consul/api v1.1.0 - github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect + github.com/hashicorp/vault v0.10.3 github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 - github.com/jonboulle/clockwork v0.1.0 // indirect github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect - github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect - github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect - github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/magiconair/properties v1.8.1 github.com/matttproud/golang_protobuf_extensions v1.0.1 github.com/mitchellh/mapstructure v1.1.2 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd - github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c + github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f github.com/opentracing/opentracing-go v1.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.1.0 - github.com/satori/go.uuid v1.2.0 + github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/soheilhy/cmux v0.1.4 // indirect github.com/stretchr/testify v1.5.1 - github.com/tebeka/strftime v0.1.3 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect - github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 go.etcd.io/bbolt v1.3.4 // indirect go.uber.org/atomic v1.6.0 go.uber.org/zap v1.15.0 + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/grpc v1.22.1 gopkg.in/yaml.v2 v2.2.2 k8s.io/api v0.0.0-20190325185214-7544f9db76f6 diff --git a/go.sum b/go.sum index f5610cb4b29d664c71c434a21c118de94f7986b1..8c8c4ef132e2c4fe5a9631f74fcabc8cae9767c9 100644 --- a/go.sum +++ b/go.sum @@ -7,7 +7,6 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX github.com/Azure/go-autorest v10.7.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v10.15.3+incompatible h1:nhKI/bvazIs3C3TFGoSqKY6hZ8f5od5mb5/UcS6HVIY= github.com/Azure/go-autorest v10.15.3+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= @@ -36,11 +35,18 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -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/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= 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.5.0 h1:fzulDG5G7nX0ccgKdiN9XipJ7tZ4WXKgmk4stdlDS6s= +github.com/apache/dubbo-go-hessian2 v1.5.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279 h1:1g3IJdaUjXWs++NA9Ail8+r6WgrkfhjS6hD/YXvRzjk= github.com/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/apache/dubbo-go-hessian2 v1.6.1 h1:mFKeCZzaCkk4mMOyP+LQ85GHbRyqKT7858KS21JQYA4= +github.com/apache/dubbo-go-hessian2 v1.6.1/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= 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= @@ -107,10 +113,13 @@ 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.5 h1:xJxdDj9jm7wlrRSsVZSk2TDNxJbbac5GpxV0QpjO+Tw= +github.com/dubbogo/getty v1.3.5/go.mod h1:T55vN8Q6tZjf2AQZiGmkujneD3LfqYbv2b3QjacwYOY= github.com/dubbogo/getty v1.3.7 h1:xlkYD2/AH34iGteuLMsGjLl2PwBVrbIhHjf3tlUsv1M= github.com/dubbogo/getty v1.3.7/go.mod h1:XWO4+wAaMqgnBN9Ykv2YxxOAkGxymg6LGO9RK+EiCDY= -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/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= +github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= +github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= @@ -136,6 +145,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-co-op/gocron v0.1.1 h1:OfDmkqkCguFtFMsm6Eaayci3DADLa8pXvdmOlPU/JcU= +github.com/go-co-op/gocron v0.1.1/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo= @@ -151,6 +162,7 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+ github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-resty/resty/v2 v2.1.0 h1:Z6IefCpUMfnvItVJaJXWv/pMiiD11So35QgwEELsldE= github.com/go-resty/resty/v2 v2.1.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-sql-driver/mysql v0.0.0-20180618115901-749ddf1598b4 h1:1LlmVz15APoKz9dnm5j2ePptburJlwEH+/v/pUuoxck= @@ -205,6 +217,8 @@ github.com/gophercloud/gophercloud v0.0.0-20180828235145-f29afc2cceca/go.mod h1: github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= @@ -388,8 +402,10 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c h1:WoCa3AvgQMVKNs+RIFlWPRgY9QVJwUxJDrGxHs0fcRo= -github.com/nacos-group/nacos-sdk-go v0.0.0-20191128082542-fe1b325b125c/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo= +github.com/nacos-group/nacos-sdk-go v0.3.2 h1:q+ukmIImL6u0zBtbceMZl2frgeAc45QT6cIrTZZz50c= +github.com/nacos-group/nacos-sdk-go v0.3.2/go.mod h1:4TdsN7eZnnVCDlOlBa61b0gsRnvNJI74m9+2+OKZkcw= +github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f h1:gid5/0AkHvINWK69Fgbidb3BVIXqlf1YEm7wO0NVPsw= +github.com/nacos-group/nacos-sdk-go v0.3.3-0.20200617023039-50c7537d6a5f/go.mod h1:fti1GlX/EB6RDKvzK/P7Vuibqj0JMPJHQwrcTU1tLXk= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= github.com/oklog/run v0.0.0-20180308005104-6934b124db28 h1:Hbr3fbVPXea52oPQeP7KLSxP52g6SFaNY1IqAmUyEW0= @@ -397,10 +413,14 @@ github.com/oklog/run v0.0.0-20180308005104-6934b124db28/go.mod h1:dlhp/R75TPv97u github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= @@ -454,8 +474,11 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880 h1:1Ge4j/3uB2rxzPWD3TC+daeCw+w91z8UCUL/7WH5gn8= @@ -508,12 +531,17 @@ github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz7 github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -525,7 +553,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -542,6 +569,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -550,6 +579,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -583,7 +614,6 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180829000535-087779f1d2c9 h1:z1TeLUmxf9ws9KLICfmX+KGXTs+rjm+aGWzfsv7MZ9w= @@ -628,7 +658,6 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= istio.io/gogo-genproto v0.0.0-20190124151557-6d926a6e6feb/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= k8s.io/api v0.0.0-20180806132203-61b11ee65332/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= diff --git a/metadata/definition/definition.go b/metadata/definition/definition.go index ead984345efde1ddd1d54b7599fd9d5584947ea2..dbbc0c8f1685edf1a26ab1fe4ad091c501e76f5f 100644 --- a/metadata/definition/definition.go +++ b/metadata/definition/definition.go @@ -17,6 +17,24 @@ package definition +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" +) + +// ServiceDefiner is a interface of service's definition +type ServiceDefiner interface { + ToBytes() ([]byte, error) +} + +// ServiceDefinition is the describer of service definition type ServiceDefinition struct { CanonicalName string CodeSource string @@ -24,6 +42,39 @@ type ServiceDefinition struct { Types []TypeDefinition } +// ToBytes convert ServiceDefinition to json string +func (def *ServiceDefinition) ToBytes() ([]byte, error) { + return json.Marshal(def) +} + +// String will iterate all methods and parameters and convert them to json string +func (def *ServiceDefinition) String() string { + var methodStr strings.Builder + for _, m := range def.Methods { + var paramType strings.Builder + for _, p := range m.ParameterTypes { + paramType.WriteString(fmt.Sprintf("{type:%v}", p)) + } + var param strings.Builder + for _, d := range m.Parameters { + param.WriteString(fmt.Sprintf("{id:%v,type:%v,builderName:%v}", d.Id, d.Type, d.TypeBuilderName)) + } + methodStr.WriteString(fmt.Sprintf("{name:%v,parameterTypes:[%v],returnType:%v,params:[%v] }", m.Name, paramType.String(), m.ReturnType, param.String())) + } + var types strings.Builder + for _, d := range def.Types { + types.WriteString(fmt.Sprintf("{id:%v,type:%v,builderName:%v}", d.Id, d.Type, d.TypeBuilderName)) + } + return fmt.Sprintf("{canonicalName:%v, codeSource:%v, methods:[%v], types:[%v]}", def.CanonicalName, def.CodeSource, methodStr.String(), types.String()) +} + +// FullServiceDefinition is the describer of service definition with parameters +type FullServiceDefinition struct { + ServiceDefinition + Params map[string]string +} + +// MethodDefinition is the describer of method definition type MethodDefinition struct { Name string ParameterTypes []string @@ -31,6 +82,7 @@ type MethodDefinition struct { Parameters []TypeDefinition } +// TypeDefinition is the describer of type definition type TypeDefinition struct { Id string Type string @@ -39,3 +91,47 @@ type TypeDefinition struct { Properties map[string]TypeDefinition TypeBuilderName string } + +// BuildServiceDefinition can build service definition which will be used to describe a service +func BuildServiceDefinition(service common.Service, url common.URL) *ServiceDefinition { + sd := &ServiceDefinition{} + sd.CanonicalName = url.Service() + + for k, m := range service.Method() { + var paramTypes []string + if len(m.ArgsType()) > 0 { + for _, t := range m.ArgsType() { + paramTypes = append(paramTypes, t.Kind().String()) + } + } + + var returnType string + if m.ReplyType() != nil { + returnType = m.ReplyType().Kind().String() + } + + methodD := MethodDefinition{ + Name: k, + ParameterTypes: paramTypes, + ReturnType: returnType, + } + sd.Methods = append(sd.Methods, methodD) + } + + return sd +} + +// ServiceDescriperBuild: build the service key, format is `group/serviceName:version` which be same as URL's service key +func ServiceDescriperBuild(serviceName string, group string, version string) string { + buf := &bytes.Buffer{} + if group != "" { + buf.WriteString(group) + buf.WriteString(constant.PATH_SEPARATOR) + } + buf.WriteString(serviceName) + if version != "" && version != "0.0.0" { + buf.WriteString(constant.KEY_SEPARATOR) + buf.WriteString(version) + } + return buf.String() +} diff --git a/metadata/definition/definition_test.go b/metadata/definition/definition_test.go new file mode 100644 index 0000000000000000000000000000000000000000..958f9324d0f9f4a5027792bd8e54b238a5f56feb --- /dev/null +++ b/metadata/definition/definition_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 definition + +import ( + "fmt" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" +) + +func TestBuildServiceDefinition(t *testing.T) { + serviceName := "com.ikurento.user.UserProvider" + group := "group1" + version := "0.0.1" + protocol := "dubbo" + beanName := "UserProvider" + url, err := common.NewURL(fmt.Sprintf( + "%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+ + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ + "environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+ + "owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245&group=%v&version=%v&bean.name=%v", + protocol, serviceName, group, version, beanName)) + assert.NoError(t, err) + _, err = common.ServiceMap.Register(serviceName, protocol, &UserProvider{}) + assert.NoError(t, err) + service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) + sd := BuildServiceDefinition(*service, url) + assert.Equal(t, "{canonicalName:com.ikurento.user.UserProvider, codeSource:, methods:[{name:GetUser,parameterTypes:[{type:slice}],returnType:ptr,params:[] }], types:[]}", sd.String()) +} diff --git a/metadata/definition/mock.go b/metadata/definition/mock.go new file mode 100644 index 0000000000000000000000000000000000000000..ca9e125a7480c2b6ff57d0b7cc820b537eb908f2 --- /dev/null +++ b/metadata/definition/mock.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 definition + +import ( + "context" + "time" +) + +type User struct { + Id string + Name string + Age int32 + Time time.Time +} + +type UserProvider struct { +} + +func (u *UserProvider) GetUser(ctx context.Context, req []interface{}) (*User, error) { + rsp := User{"A001", "Alex Stocks", 18, time.Now()} + return &rsp, nil +} + +func (u *UserProvider) Reference() string { + return "UserProvider" +} + +func (u User) JavaClassName() string { + return "com.ikurento.user.User" +} diff --git a/metadata/identifier/base_metadata_identifier.go b/metadata/identifier/base_metadata_identifier.go index 5f3df4c607e69d2b56e1258d081c148524cd7aca..2371f7ca02f403a11251b9b0cbb23369b27683e2 100644 --- a/metadata/identifier/base_metadata_identifier.go +++ b/metadata/identifier/base_metadata_identifier.go @@ -25,21 +25,21 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -// BaseMetadataIdentifier defined for description the Metadata base identify -type BaseMetadataIdentifier interface { - getFilePathKey(params ...string) string - getIdentifierKey(params ...string) string +// BaseMetadataIdentifier defined for describe the Metadata base identify +type IMetadataIdentifier interface { + GetFilePathKey() string + GetIdentifierKey() string } // BaseMetadataIdentifier is the base implement of BaseMetadataIdentifier interface -type BaseServiceMetadataIdentifier struct { - serviceInterface string - version string - group string - side string +type BaseMetadataIdentifier struct { + ServiceInterface string + Version string + Group string + Side string } -// joinParams will join the specified char in slice, and return a string +// joinParams will join the specified char in slice, and build as string func joinParams(joinChar string, params []string) string { var joinedStr string for _, param := range params { @@ -50,27 +50,28 @@ func joinParams(joinChar string, params []string) string { } // getIdentifierKey returns string that format is service:Version:Group:Side:param1:param2... -func (mdi *BaseServiceMetadataIdentifier) getIdentifierKey(params ...string) string { - return mdi.serviceInterface + - constant.KEY_SEPARATOR + mdi.version + - constant.KEY_SEPARATOR + mdi.group + - constant.KEY_SEPARATOR + mdi.side + +func (mdi *BaseMetadataIdentifier) getIdentifierKey(params ...string) string { + return mdi.ServiceInterface + + constant.KEY_SEPARATOR + mdi.Version + + constant.KEY_SEPARATOR + mdi.Group + + constant.KEY_SEPARATOR + mdi.Side + joinParams(constant.KEY_SEPARATOR, params) } // getFilePathKey returns string that format is metadata/path/Version/Group/Side/param1/param2... -func (mdi *BaseServiceMetadataIdentifier) getFilePathKey(params ...string) string { - path := serviceToPath(mdi.serviceInterface) +func (mdi *BaseMetadataIdentifier) getFilePathKey(params ...string) string { + path := serviceToPath(mdi.ServiceInterface) return constant.DEFAULT_PATH_TAG + withPathSeparator(path) + - withPathSeparator(mdi.version) + - withPathSeparator(mdi.group) + - withPathSeparator(mdi.side) + + withPathSeparator(mdi.Version) + + withPathSeparator(mdi.Group) + + withPathSeparator(mdi.Side) + joinParams(constant.PATH_SEPARATOR, params) } +// serviceToPath uss URL encode to decode the @serviceInterface func serviceToPath(serviceInterface string) string { if serviceInterface == constant.ANY_VALUE { return "" @@ -84,6 +85,7 @@ func serviceToPath(serviceInterface string) string { } +// withPathSeparator return "/" + @path func withPathSeparator(path string) string { if len(path) != 0 { path = constant.PATH_SEPARATOR + path diff --git a/metadata/identifier/base_metadata_identifier_test.go b/metadata/identifier/base_metadata_identifier_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5b60992ab6132ecb306245af31bba7e3d0f09117 --- /dev/null +++ b/metadata/identifier/base_metadata_identifier_test.go @@ -0,0 +1,41 @@ +/* + * 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 identifier + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +var baseId = &BaseMetadataIdentifier{ + ServiceInterface: "org.apache.pkg.mockService", + Version: "1.0.0", + Group: "Group", + Side: "provider", +} + +func TestBaseGetFilePathKey(t *testing.T) { + assert.Equal(t, "metadata/1.0.0/Group/provider/a/b/c", baseId.getFilePathKey("a", "b", "c")) +} + +func TestBaseGetIdentifierKey(t *testing.T) { + assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:a:b:c", baseId.getIdentifierKey("a", "b", "c")) +} diff --git a/metadata/identifier/metadata_identifier.go b/metadata/identifier/metadata_identifier.go index 7e72c10da9c088ca167fa4fbc4dcb57f44b8c06d..7e50c4c6b9427bd9d439daa7464d96a2ea94fd39 100644 --- a/metadata/identifier/metadata_identifier.go +++ b/metadata/identifier/metadata_identifier.go @@ -19,16 +19,16 @@ package identifier // MetadataIdentifier is inherit baseMetaIdentifier with Application name type MetadataIdentifier struct { - application string + Application string BaseMetadataIdentifier } // GetIdentifierKey returns string that format is service:Version:Group:Side:Application -func (mdi *MetadataIdentifier) getIdentifierKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.application) +func (mdi *MetadataIdentifier) GetIdentifierKey() string { + return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Application) } // GetFilePathKey returns string that format is metadata/path/Version/Group/Side/Application -func (mdi *MetadataIdentifier) getFilePathKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.application) +func (mdi *MetadataIdentifier) GetFilePathKey() string { + return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.Application) } diff --git a/metadata/identifier/metadata_identifier_test.go b/metadata/identifier/metadata_identifier_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cba3c0dd76a01f2125b87db4478f99501bf2c284 --- /dev/null +++ b/metadata/identifier/metadata_identifier_test.go @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package identifier + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +var metadataId = &MetadataIdentifier{ + Application: "app", + BaseMetadataIdentifier: BaseMetadataIdentifier{ + ServiceInterface: "org.apache.pkg.mockService", + Version: "1.0.0", + Group: "Group", + Side: "provider", + }, +} + +func TestGetFilePathKey(t *testing.T) { + assert.Equal(t, "metadata/1.0.0/Group/provider/app", metadataId.GetFilePathKey()) +} + +func TestGetIdentifierKey(t *testing.T) { + assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:app", metadataId.GetIdentifierKey()) +} diff --git a/metadata/identifier/service_metadata_identifier.go b/metadata/identifier/service_metadata_identifier.go index ccc149f7306c125b19a25373d4da660a154cc84e..b9e65967e0f707a6efcc9f8ded2ce5dec4f058b8 100644 --- a/metadata/identifier/service_metadata_identifier.go +++ b/metadata/identifier/service_metadata_identifier.go @@ -18,22 +18,38 @@ package identifier import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" ) // ServiceMetadataIdentifier is inherit baseMetaIdentifier with service params: Revision and Protocol type ServiceMetadataIdentifier struct { - revision string - protocol string + Revision string + Protocol string BaseMetadataIdentifier } +// NewServiceMetadataIdentifier create instance. +// The ServiceInterface is the @url.Service() +// other parameters are read from @url +func NewServiceMetadataIdentifier(url common.URL) *ServiceMetadataIdentifier { + return &ServiceMetadataIdentifier{ + BaseMetadataIdentifier: BaseMetadataIdentifier{ + ServiceInterface: url.Service(), + Version: url.GetParam(constant.VERSION_KEY, ""), + Group: url.GetParam(constant.GROUP_KEY, ""), + Side: url.GetParam(constant.SIDE_KEY, ""), + }, + Protocol: url.Protocol, + } +} + // GetIdentifierKey returns string that format is service:Version:Group:Side:Protocol:"revision"+Revision -func (mdi *ServiceMetadataIdentifier) getIdentifierKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision) +func (mdi *ServiceMetadataIdentifier) GetIdentifierKey() string { + return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Protocol, constant.KEY_REVISON_PREFIX+mdi.Revision) } // GetFilePathKey returns string that format is metadata/path/Version/Group/Side/Protocol/"revision"+Revision -func (mdi *ServiceMetadataIdentifier) getFilePathKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision) +func (mdi *ServiceMetadataIdentifier) GetFilePathKey() string { + return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.Protocol, constant.KEY_REVISON_PREFIX+mdi.Revision) } diff --git a/metadata/identifier/service_metadata_identifier_test.go b/metadata/identifier/service_metadata_identifier_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d7ef44a4bbc7611b6391122f8f5841db349eb036 --- /dev/null +++ b/metadata/identifier/service_metadata_identifier_test.go @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package identifier + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +var serviceMetadataId = &ServiceMetadataIdentifier{ + Revision: "1.0", + Protocol: "dubbo", + BaseMetadataIdentifier: BaseMetadataIdentifier{ + ServiceInterface: "org.apache.pkg.mockService", + Version: "1.0.0", + Group: "Group", + Side: "provider", + }, +} + +func TestServiceGetFilePathKey(t *testing.T) { + assert.Equal(t, "metadata/1.0.0/Group/provider/dubbo/revision1.0", serviceMetadataId.GetFilePathKey()) +} + +func TestServiceGetIdentifierKey(t *testing.T) { + assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:dubbo:revision1.0", serviceMetadataId.GetIdentifierKey()) +} diff --git a/metadata/identifier/subscribe_metadata_identifier.go b/metadata/identifier/subscribe_metadata_identifier.go index 38f3ebbd462338b581d83cd19403a00a5064b5a4..b1e37db971ada56a77bc3b716606b6fc8d137d34 100644 --- a/metadata/identifier/subscribe_metadata_identifier.go +++ b/metadata/identifier/subscribe_metadata_identifier.go @@ -19,16 +19,16 @@ package identifier // SubscriberMetadataIdentifier is inherit baseMetaIdentifier with service params: Revision type SubscriberMetadataIdentifier struct { - revision string - BaseMetadataIdentifier + Revision string + MetadataIdentifier } // GetIdentifierKey returns string that format is service:Version:Group:Side:Revision -func (mdi *SubscriberMetadataIdentifier) getIdentifierKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.revision) +func (mdi *SubscriberMetadataIdentifier) GetIdentifierKey() string { + return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Revision) } // GetFilePathKey returns string that format is metadata/path/Version/Group/Side/Revision -func (mdi *SubscriberMetadataIdentifier) getFilePathKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.revision) +func (mdi *SubscriberMetadataIdentifier) GetFilePathKey() string { + return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.Revision) } diff --git a/metadata/identifier/subscribe_metadata_identifier_test.go b/metadata/identifier/subscribe_metadata_identifier_test.go new file mode 100644 index 0000000000000000000000000000000000000000..215aa3c5691f20d7790029093372389ce620398c --- /dev/null +++ b/metadata/identifier/subscribe_metadata_identifier_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 identifier + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +var subscribeMetadataId = &SubscriberMetadataIdentifier{ + Revision: "1.0", + MetadataIdentifier: MetadataIdentifier{ + BaseMetadataIdentifier: BaseMetadataIdentifier{ + ServiceInterface: "org.apache.pkg.mockService", + Version: "1.0.0", + Group: "Group", + Side: "provider", + }, + }, +} + +func TestSubscribeGetFilePathKey(t *testing.T) { + assert.Equal(t, "metadata/1.0.0/Group/provider/1.0", subscribeMetadataId.GetFilePathKey()) +} + +func TestSubscribeGetIdentifierKey(t *testing.T) { + assert.Equal(t, "org.apache.pkg.mockService:1.0.0:Group:provider:1.0", subscribeMetadataId.GetIdentifierKey()) +} diff --git a/metadata/namemapping/dynamic/service_name_mapping.go b/metadata/mapping/dynamic/service_name_mapping.go similarity index 75% rename from metadata/namemapping/dynamic/service_name_mapping.go rename to metadata/mapping/dynamic/service_name_mapping.go index e93c256fe093b4a3e3c431e1d012038b2bb7976b..84039ace9a2d56eca96bf36afc46d28e2a5ebe60 100644 --- a/metadata/namemapping/dynamic/service_name_mapping.go +++ b/metadata/mapping/dynamic/service_name_mapping.go @@ -19,6 +19,7 @@ package dynamic import ( "strconv" + "sync" "time" ) @@ -28,18 +29,26 @@ import ( ) import ( + commonCfg "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/common/logger" "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/config_center" - "github.com/apache/dubbo-go/metadata" + "github.com/apache/dubbo-go/metadata/mapping" ) const ( - defaultGroup = config_center.DEFAULT_GROUP + defaultGroup = "mapping" slash = "/" ) +func init() { + extension.SetGlobalServiceNameMapping(GetNameMappingInstance) +} + // DynamicConfigurationServiceNameMapping is the implementation based on config center +// it's a singleton type DynamicConfigurationServiceNameMapping struct { dc config_center.DynamicConfiguration } @@ -48,7 +57,8 @@ type DynamicConfigurationServiceNameMapping struct { func (d *DynamicConfigurationServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error { // metadata service is admin service, should not be mapped if constant.METADATA_SERVICE_NAME == serviceInterface { - return perrors.New("try to map the metadata service, will be ignored") + logger.Info("try to map the metadata service, will be ignored") + return nil } appName := config.GetApplicationConfig().Name @@ -76,7 +86,16 @@ func (d *DynamicConfigurationServiceNameMapping) buildGroup(serviceInterface str return defaultGroup + slash + serviceInterface } -// NewServiceNameMapping will create an instance of DynamicConfigurationServiceNameMapping -func NewServiceNameMapping(dc config_center.DynamicConfiguration) metadata.ServiceNameMapping { - return &DynamicConfigurationServiceNameMapping{dc: dc} +var ( + serviceNameMappingInstance *DynamicConfigurationServiceNameMapping + serviceNameMappingOnce sync.Once +) + +// GetNameMappingInstance return an instance, if not found, it creates one +func GetNameMappingInstance() mapping.ServiceNameMapping { + serviceNameMappingOnce.Do(func() { + dc := commonCfg.GetEnvInstance().GetDynamicConfiguration() + serviceNameMappingInstance = &DynamicConfigurationServiceNameMapping{dc: dc} + }) + return serviceNameMappingInstance } diff --git a/metadata/namemapping/dynamic/service_name_mapping_test.go b/metadata/mapping/dynamic/service_name_mapping_test.go similarity index 95% rename from metadata/namemapping/dynamic/service_name_mapping_test.go rename to metadata/mapping/dynamic/service_name_mapping_test.go index e3d620cd738421c256d8fd232b1afcfd425ca989..2896b0fd4aa4fb6bada132c276c70a1653e59f99 100644 --- a/metadata/namemapping/dynamic/service_name_mapping_test.go +++ b/metadata/mapping/dynamic/service_name_mapping_test.go @@ -41,14 +41,14 @@ func TestDynamicConfigurationServiceNameMapping(t *testing.T) { }).GetDynamicConfiguration(nil) config.GetApplicationConfig().Name = appName - mapping := NewServiceNameMapping(dc) + mapping := &DynamicConfigurationServiceNameMapping{dc: dc} intf := constant.METADATA_SERVICE_NAME group := "myGroup" version := "myVersion" protocol := "myProtocol" err = mapping.Map(intf, group, version, protocol) - assert.NotNil(t, err) + assert.Nil(t, err) intf = "MyService" err = mapping.Map(intf, group, version, protocol) assert.Nil(t, err) diff --git a/metadata/mapping/memory/service_name_mapping.go b/metadata/mapping/memory/service_name_mapping.go new file mode 100644 index 0000000000000000000000000000000000000000..0965d52d91e047215abd9b7d14523ecaa833f0ed --- /dev/null +++ b/metadata/mapping/memory/service_name_mapping.go @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package memory + +import ( + "sync" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" +) +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/mapping" +) + +func init() { + extension.SetGlobalServiceNameMapping(GetNameMappingInstance) +} + +type InMemoryServiceNameMapping struct{} + +func (i *InMemoryServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error { + return nil +} + +func (i *InMemoryServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) { + return gxset.NewSet(config.GetApplicationConfig().Name), nil +} + +var serviceNameMappingInstance *InMemoryServiceNameMapping +var serviceNameMappingOnce sync.Once + +func GetNameMappingInstance() mapping.ServiceNameMapping { + serviceNameMappingOnce.Do(func() { + serviceNameMappingInstance = &InMemoryServiceNameMapping{} + }) + return serviceNameMappingInstance +} diff --git a/metadata/service_name_mapping.go b/metadata/mapping/service_name_mapping.go similarity index 98% rename from metadata/service_name_mapping.go rename to metadata/mapping/service_name_mapping.go index c14e8ce2e7c40d1573897dfd6ba64c16e18acac7..6caed9f0b48c1bb9c2f0f1026eb642f69bb31113 100644 --- a/metadata/service_name_mapping.go +++ b/metadata/mapping/service_name_mapping.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package metadata +package mapping import ( gxset "github.com/dubbogo/gost/container/set" diff --git a/metadata/report/consul/report.go b/metadata/report/consul/report.go new file mode 100644 index 0000000000000000000000000000000000000000..eb2bdc25ecec596a8f89abb80856b8d6e7be70a4 --- /dev/null +++ b/metadata/report/consul/report.go @@ -0,0 +1,125 @@ +/* + * 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 consul + +import ( + consul "github.com/hashicorp/consul/api" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/report/factory" +) + +var ( + emptyStrSlice = make([]string, 0) +) + +func init() { + mf := &consulMetadataReportFactory{} + extension.SetMetadataReportFactory("consul", func() factory.MetadataReportFactory { + return mf + }) +} + +// consulMetadataReport is the implementation of +// MetadataReport based on consul. +type consulMetadataReport struct { + client *consul.Client +} + +// StoreProviderMetadata stores the metadata. +func (m *consulMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error { + kv := &consul.KVPair{Key: providerIdentifier.GetIdentifierKey(), Value: []byte(serviceDefinitions)} + _, err := m.client.KV().Put(kv, nil) + return err +} + +// StoreConsumerMetadata stores the metadata. +func (m *consulMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error { + kv := &consul.KVPair{Key: consumerMetadataIdentifier.GetIdentifierKey(), Value: []byte(serviceParameterString)} + _, err := m.client.KV().Put(kv, nil) + return err +} + +// SaveServiceMetadata saves the metadata. +func (m *consulMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error { + kv := &consul.KVPair{Key: metadataIdentifier.GetIdentifierKey(), Value: []byte(url.String())} + _, err := m.client.KV().Put(kv, nil) + return err +} + +// RemoveServiceMetadata removes the metadata. +func (m *consulMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error { + k := metadataIdentifier.GetIdentifierKey() + _, err := m.client.KV().Delete(k, nil) + return err +} + +// GetExportedURLs gets the urls. +func (m *consulMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) { + k := metadataIdentifier.GetIdentifierKey() + kv, _, err := m.client.KV().Get(k, nil) + if err != nil || kv == nil { + return emptyStrSlice, err + } + return []string{string(kv.Value)}, nil +} + +// SaveSubscribedData saves the urls. +func (m *consulMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error { + kv := &consul.KVPair{Key: subscriberMetadataIdentifier.GetIdentifierKey(), Value: []byte(urls)} + _, err := m.client.KV().Put(kv, nil) + return err +} + +// GetSubscribedURLs gets the urls. +func (m *consulMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { + k := subscriberMetadataIdentifier.GetIdentifierKey() + kv, _, err := m.client.KV().Get(k, nil) + if err != nil || kv == nil { + return emptyStrSlice, err + } + return []string{string(kv.Value)}, nil +} + +// GetServiceDefinition gets the service definition. +func (m *consulMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) { + k := metadataIdentifier.GetIdentifierKey() + kv, _, err := m.client.KV().Get(k, nil) + if err != nil || kv == nil { + return "", err + } + return string(kv.Value), nil +} + +type consulMetadataReportFactory struct { +} + +// nolint +func (mf *consulMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport { + config := &consul.Config{Address: url.Location} + client, err := consul.NewClient(config) + if err != nil { + panic(err) + } + return &consulMetadataReport{client: client} +} diff --git a/metadata/report/consul/report_test.go b/metadata/report/consul/report_test.go new file mode 100644 index 0000000000000000000000000000000000000000..34ee29de945f2b9ac6978a55008048e62f4c6812 --- /dev/null +++ b/metadata/report/consul/report_test.go @@ -0,0 +1,165 @@ +/* + * 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 consul + +import ( + "encoding/json" + "net/url" + "strconv" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/remoting/consul" +) + +func newProviderRegistryUrl(host string, port int) *common.URL { + return common.NewURLWithOptions( + common.WithIp(host), + common.WithPort(strconv.Itoa(port)), + common.WithParams(url.Values{}), + common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)), + ) +} + +func newBaseMetadataIdentifier(side string) *identifier.BaseMetadataIdentifier { + return &identifier.BaseMetadataIdentifier{ + ServiceInterface: "org.apache.HelloWorld", + Version: "1.0.0", + Group: "group", + Side: side, + } +} + +func newMetadataIdentifier(side string) *identifier.MetadataIdentifier { + return &identifier.MetadataIdentifier{ + Application: "application", + BaseMetadataIdentifier: *newBaseMetadataIdentifier(side), + } +} + +func newServiceMetadataIdentifier(side string) *identifier.ServiceMetadataIdentifier { + return &identifier.ServiceMetadataIdentifier{ + Revision: "1.0", + Protocol: "dubbo", + BaseMetadataIdentifier: *newBaseMetadataIdentifier(side), + } +} + +func newSubscribeMetadataIdentifier(side string) *identifier.SubscriberMetadataIdentifier { + return &identifier.SubscriberMetadataIdentifier{ + Revision: "1.0", + MetadataIdentifier: *newMetadataIdentifier(side), + } +} + +type consulMetadataReportTestSuite struct { + t *testing.T + m report.MetadataReport +} + +func newConsulMetadataReportTestSuite(t *testing.T, m report.MetadataReport) *consulMetadataReportTestSuite { + return &consulMetadataReportTestSuite{t: t, m: m} +} + +func (suite *consulMetadataReportTestSuite) testStoreProviderMetadata() { + providerMi := newMetadataIdentifier("provider") + providerMeta := "provider" + err := suite.m.StoreProviderMetadata(providerMi, providerMeta) + assert.NoError(suite.t, err) +} + +func (suite *consulMetadataReportTestSuite) testStoreConsumerMetadata() { + consumerMi := newMetadataIdentifier("consumer") + consumerMeta := "consumer" + err := suite.m.StoreProviderMetadata(consumerMi, consumerMeta) + assert.NoError(suite.t, err) +} + +func (suite *consulMetadataReportTestSuite) testSaveServiceMetadata(url common.URL) { + serviceMi := newServiceMetadataIdentifier("provider") + err := suite.m.SaveServiceMetadata(serviceMi, url) + assert.NoError(suite.t, err) +} + +func (suite *consulMetadataReportTestSuite) testRemoveServiceMetadata() { + serviceMi := newServiceMetadataIdentifier("provider") + err := suite.m.RemoveServiceMetadata(serviceMi) + assert.NoError(suite.t, err) +} + +func (suite *consulMetadataReportTestSuite) testGetExportedURLs() { + serviceMi := newServiceMetadataIdentifier("provider") + urls, err := suite.m.GetExportedURLs(serviceMi) + assert.Equal(suite.t, 1, len(urls)) + assert.NoError(suite.t, err) +} + +func (suite *consulMetadataReportTestSuite) testSaveSubscribedData(url common.URL) { + subscribeMi := newSubscribeMetadataIdentifier("provider") + urls := []string{url.String()} + bytes, _ := json.Marshal(urls) + err := suite.m.SaveSubscribedData(subscribeMi, string(bytes)) + assert.Nil(suite.t, err) +} + +func (suite *consulMetadataReportTestSuite) testGetSubscribedURLs() { + subscribeMi := newSubscribeMetadataIdentifier("provider") + urls, err := suite.m.GetSubscribedURLs(subscribeMi) + assert.Equal(suite.t, 1, len(urls)) + assert.NoError(suite.t, err) +} + +func (suite *consulMetadataReportTestSuite) testGetServiceDefinition() { + providerMi := newMetadataIdentifier("provider") + providerMeta, err := suite.m.GetServiceDefinition(providerMi) + assert.Equal(suite.t, "provider", providerMeta) + assert.NoError(suite.t, err) +} + +func test1(t *testing.T) { + consulAgent := consul.NewConsulAgent(t, 8500) + defer consulAgent.Close() + + url := newProviderRegistryUrl("localhost", 8500) + mf := extension.GetMetadataReportFactory("consul") + m := mf.CreateMetadataReport(url) + + suite := newConsulMetadataReportTestSuite(t, m) + suite.testStoreProviderMetadata() + suite.testStoreConsumerMetadata() + suite.testSaveServiceMetadata(*url) + suite.testGetExportedURLs() + suite.testRemoveServiceMetadata() + suite.testSaveSubscribedData(*url) + suite.testGetSubscribedURLs() + suite.testGetServiceDefinition() +} + +func TestConsulMetadataReport(t *testing.T) { + t.Run("test1", test1) +} diff --git a/metadata/report/delegate/delegate_report.go b/metadata/report/delegate/delegate_report.go new file mode 100644 index 0000000000000000000000000000000000000000..cdd29ab2e483647ed90f37032e10d174f7583e39 --- /dev/null +++ b/metadata/report/delegate/delegate_report.go @@ -0,0 +1,287 @@ +/* + * 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 delegate + +import ( + "encoding/json" + "runtime/debug" + "sync" + "time" +) + +import ( + "github.com/go-co-op/gocron" + perrors "github.com/pkg/errors" + "go.uber.org/atomic" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config/instance" + "github.com/apache/dubbo-go/metadata/definition" + "github.com/apache/dubbo-go/metadata/identifier" +) + +const ( + // defaultMetadataReportRetryTimes is defined for max times to retry + defaultMetadataReportRetryTimes int64 = 100 + // defaultMetadataReportRetryPeriod is defined for cycle interval to retry, the unit is second + defaultMetadataReportRetryPeriod int64 = 3 + // defaultMetadataReportRetryPeriod is defined for cycle report or not + defaultMetadataReportCycleReport bool = true +) + +// metadataReportRetry is a scheduler for retrying task +type metadataReportRetry struct { + retryPeriod int64 + retryLimit int64 + scheduler *gocron.Scheduler + job *gocron.Job + retryCounter *atomic.Int64 + // if no failed report, wait how many times to run retry task. + retryTimesIfNonFail int64 +} + +// newMetadataReportRetry will create a scheduler for retry task +func newMetadataReportRetry(retryPeriod int64, retryLimit int64, retryFunc func() bool) (*metadataReportRetry, error) { + s1 := gocron.NewScheduler(time.UTC) + + mrr := &metadataReportRetry{ + retryPeriod: retryPeriod, + retryLimit: retryLimit, + scheduler: s1, + retryCounter: atomic.NewInt64(0), + retryTimesIfNonFail: 600, + } + + newJob, err := mrr.scheduler.Every(uint64(mrr.retryPeriod)).Seconds().Do( + func() { + mrr.retryCounter.Inc() + logger.Infof("start to retry task for metadata report. retry times: %v", mrr.retryCounter.Load()) + if mrr.retryCounter.Load() > mrr.retryLimit { + mrr.scheduler.Clear() + } else if retryFunc() && mrr.retryCounter.Load() > mrr.retryTimesIfNonFail { + mrr.scheduler.Clear() // may not interrupt the running job + } + }) + + mrr.job = newJob + return mrr, err +} + +// startRetryTask will make scheduler with retry task run +func (mrr *metadataReportRetry) startRetryTask() { + mrr.scheduler.StartAt(time.Now().Add(500 * time.Millisecond)) + mrr.scheduler.Start() +} + +// MetadataReport is a absolute delegate for MetadataReport +type MetadataReport struct { + reportUrl common.URL + syncReport bool + metadataReportRetry *metadataReportRetry + + failedReports map[*identifier.MetadataIdentifier]interface{} + failedReportsLock sync.RWMutex + + // allMetadataReports store all the metdadata reports records in memory + allMetadataReports map[*identifier.MetadataIdentifier]interface{} + allMetadataReportsLock sync.RWMutex +} + +// NewMetadataReport will create a MetadataReport with initiation +func NewMetadataReport() (*MetadataReport, error) { + url := instance.GetMetadataReportUrl() + bmr := &MetadataReport{ + reportUrl: url, + syncReport: url.GetParamBool(constant.SYNC_REPORT_KEY, false), + failedReports: make(map[*identifier.MetadataIdentifier]interface{}, 4), + allMetadataReports: make(map[*identifier.MetadataIdentifier]interface{}, 4), + } + + mrr, err := newMetadataReportRetry( + url.GetParamInt(constant.RETRY_PERIOD_KEY, defaultMetadataReportRetryPeriod), + url.GetParamInt(constant.RETRY_TIMES_KEY, defaultMetadataReportRetryTimes), + bmr.retry, + ) + + if err != nil { + return nil, err + } + + bmr.metadataReportRetry = mrr + if url.GetParamBool(constant.CYCLE_REPORT_KEY, defaultMetadataReportCycleReport) { + scheduler := gocron.NewScheduler(time.UTC) + _, err := scheduler.Every(1).Day().Do( + func() { + logger.Infof("start to publish all metadata in metadata report %s.", url.String()) + bmr.allMetadataReportsLock.RLock() + bmr.doHandlerMetadataCollection(bmr.allMetadataReports) + bmr.allMetadataReportsLock.RUnlock() + + }) + if err != nil { + return nil, err + } + scheduler.StartAt(time.Now().Add(500 * time.Millisecond)) + scheduler.Start() + } + return bmr, nil +} + +// retry will do metadata failed reports collection by call metadata report sdk +func (mr *MetadataReport) retry() bool { + mr.failedReportsLock.RLock() + defer mr.failedReportsLock.RUnlock() + return mr.doHandlerMetadataCollection(mr.failedReports) +} + +// StoreProviderMetadata will delegate to call remote metadata's sdk to store provider service definition +func (mr *MetadataReport) StoreProviderMetadata(identifier *identifier.MetadataIdentifier, definer definition.ServiceDefiner) { + if mr.syncReport { + mr.storeMetadataTask(common.PROVIDER, identifier, definer) + } + go mr.storeMetadataTask(common.PROVIDER, identifier, definer) +} + +// storeMetadataTask will delegate to call remote metadata's sdk to store +func (mr *MetadataReport) storeMetadataTask(role int, identifier *identifier.MetadataIdentifier, definer interface{}) { + logger.Infof("store provider metadata. Identifier :%v ; definition: %v .", identifier, definer) + mr.allMetadataReportsLock.Lock() + mr.allMetadataReports[identifier] = definer + mr.allMetadataReportsLock.Unlock() + + mr.failedReportsLock.Lock() + delete(mr.failedReports, identifier) + mr.failedReportsLock.Unlock() + // data is store the json marshaled definition + var ( + data []byte + err error + ) + + defer func() { + if r := recover(); r != nil { + mr.failedReportsLock.Lock() + mr.failedReports[identifier] = definer + mr.failedReportsLock.Unlock() + mr.metadataReportRetry.startRetryTask() + logger.Errorf("Failed to put provider metadata %v in %v, cause: %v\n%s\n", + identifier, string(data), r, string(debug.Stack())) + } + }() + + data, err = json.Marshal(definer) + if err != nil { + logger.Errorf("storeProviderMetadataTask error in stage json.Marshal, msg is %+v", err) + panic(err) + } + report := instance.GetMetadataReportInstance() + if role == common.PROVIDER { + err = report.StoreProviderMetadata(identifier, string(data)) + } else if role == common.CONSUMER { + err = report.StoreConsumerMetadata(identifier, string(data)) + } + + if err != nil { + logger.Errorf("storeProviderMetadataTask error in stage call metadata report to StoreProviderMetadata, msg is %+v", err) + panic(err) + } +} + +// StoreConsumerMetadata will delegate to call remote metadata's sdk to store consumer side service definition +func (mr *MetadataReport) StoreConsumerMetadata(identifier *identifier.MetadataIdentifier, definer map[string]string) { + if mr.syncReport { + mr.storeMetadataTask(common.CONSUMER, identifier, definer) + } + go mr.storeMetadataTask(common.CONSUMER, identifier, definer) +} + +// SaveServiceMetadata will delegate to call remote metadata's sdk to save service metadata +func (mr *MetadataReport) SaveServiceMetadata(identifier *identifier.ServiceMetadataIdentifier, url common.URL) error { + report := instance.GetMetadataReportInstance() + if mr.syncReport { + return report.SaveServiceMetadata(identifier, url) + } + go report.SaveServiceMetadata(identifier, url) + return nil +} + +// RemoveServiceMetadata will delegate to call remote metadata's sdk to remove service metadata +func (mr *MetadataReport) RemoveServiceMetadata(identifier *identifier.ServiceMetadataIdentifier) error { + report := instance.GetMetadataReportInstance() + if mr.syncReport { + return report.RemoveServiceMetadata(identifier) + } + go report.RemoveServiceMetadata(identifier) + return nil +} + +// GetExportedURLs will delegate to call remote metadata's sdk to get exported urls +func (mr *MetadataReport) GetExportedURLs(identifier *identifier.ServiceMetadataIdentifier) ([]string, error) { + report := instance.GetMetadataReportInstance() + return report.GetExportedURLs(identifier) +} + +// SaveSubscribedData will delegate to call remote metadata's sdk to save subscribed data +func (mr *MetadataReport) SaveSubscribedData(identifier *identifier.SubscriberMetadataIdentifier, urls []common.URL) error { + urlStrList := make([]string, 0, len(urls)) + for _, url := range urls { + urlStrList = append(urlStrList, url.String()) + } + bytes, err := json.Marshal(urlStrList) + if err != nil { + return perrors.WithMessage(err, "Could not convert the array to json") + } + + report := instance.GetMetadataReportInstance() + if mr.syncReport { + return report.SaveSubscribedData(identifier, string(bytes)) + } + go report.SaveSubscribedData(identifier, string(bytes)) + return nil +} + +// GetSubscribedURLs will delegate to call remote metadata's sdk to get subscribed urls +func (MetadataReport) GetSubscribedURLs(identifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { + report := instance.GetMetadataReportInstance() + return report.GetSubscribedURLs(identifier) +} + +// GetServiceDefinition will delegate to call remote metadata's sdk to get service definitions +func (MetadataReport) GetServiceDefinition(identifier *identifier.MetadataIdentifier) (string, error) { + report := instance.GetMetadataReportInstance() + return report.GetServiceDefinition(identifier) +} + +// doHandlerMetadataCollection will store metadata to metadata support with given metadataMap +func (mr *MetadataReport) doHandlerMetadataCollection(metadataMap map[*identifier.MetadataIdentifier]interface{}) bool { + if len(metadataMap) == 0 { + return true + } + for e := range metadataMap { + if common.RoleType(common.PROVIDER).Role() == e.Side { + mr.StoreProviderMetadata(e, metadataMap[e].(*definition.ServiceDefinition)) + } else if common.RoleType(common.CONSUMER).Role() == e.Side { + mr.StoreConsumerMetadata(e, metadataMap[e].(map[string]string)) + } + } + return false +} diff --git a/metadata/report/delegate/delegate_report_test.go b/metadata/report/delegate/delegate_report_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3dfca577ba06598b90c553048777951c8823b256 --- /dev/null +++ b/metadata/report/delegate/delegate_report_test.go @@ -0,0 +1,123 @@ +/* + * 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 delegate + +import ( + "fmt" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" + "go.uber.org/atomic" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config/instance" + "github.com/apache/dubbo-go/metadata/definition" + "github.com/apache/dubbo-go/metadata/identifier" +) + +func TestMetadataReport_MetadataReportRetry(t *testing.T) { + counter := atomic.NewInt64(1) + + retry, err := newMetadataReportRetry(1, 10, func() bool { + counter.Add(1) + return true + }) + assert.NoError(t, err) + retry.startRetryTask() + itsTime := time.After(2500 * time.Millisecond) + select { + case <-itsTime: + retry.scheduler.Clear() + assert.Equal(t, counter.Load(), int64(3)) + logger.Info("over") + } +} + +func TestMetadataReport_MetadataReportRetryWithLimit(t *testing.T) { + counter := atomic.NewInt64(1) + + retry, err := newMetadataReportRetry(1, 1, func() bool { + counter.Add(1) + return true + }) + assert.NoError(t, err) + retry.startRetryTask() + itsTime := time.After(2500 * time.Millisecond) + select { + case <-itsTime: + retry.scheduler.Clear() + assert.Equal(t, counter.Load(), int64(2)) + logger.Info("over") + } +} + +func mockNewMetadataReport(t *testing.T) *MetadataReport { + syncReportKey := "false" + retryPeriodKey := "3" + retryTimesKey := "100" + cycleReportKey := "true" + + url, err := common.NewURL(fmt.Sprintf( + "test://127.0.0.1:20000/?"+constant.SYNC_REPORT_KEY+"=%v&"+constant.RETRY_PERIOD_KEY+"=%v&"+ + constant.RETRY_TIMES_KEY+"=%v&"+constant.CYCLE_REPORT_KEY+"=%v", + syncReportKey, retryPeriodKey, retryTimesKey, cycleReportKey)) + assert.NoError(t, err) + instance.SetMetadataReportUrl(url) + mtr, err := NewMetadataReport() + assert.NoError(t, err) + assert.NotNil(t, mtr) + return mtr +} + +func TestMetadataReport_StoreProviderMetadata(t *testing.T) { + mtr := mockNewMetadataReport(t) + var metadataId = &identifier.MetadataIdentifier{ + Application: "app", + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: "com.ikurento.user.UserProvider", + Version: "0.0.1", + Group: "group1", + Side: "provider", + }, + } + + mtr.StoreProviderMetadata(metadataId, getMockDefinition(metadataId, t)) +} + +func getMockDefinition(id *identifier.MetadataIdentifier, t *testing.T) *definition.ServiceDefinition { + protocol := "dubbo" + beanName := "UserProvider" + url, err := common.NewURL(fmt.Sprintf( + "%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+ + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ + "environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+ + "owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245&group=%v&version=%v&bean.name=%v", + protocol, id.ServiceInterface, id.Group, id.Version, beanName)) + assert.NoError(t, err) + _, err = common.ServiceMap.Register(id.ServiceInterface, protocol, &definition.UserProvider{}) + assert.NoError(t, err) + service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) + return definition.BuildServiceDefinition(*service, url) +} diff --git a/metadata/report/etcd/report.go b/metadata/report/etcd/report.go new file mode 100644 index 0000000000000000000000000000000000000000..097835c986962850d137255ec807b417de1484ad --- /dev/null +++ b/metadata/report/etcd/report.go @@ -0,0 +1,142 @@ +/* + * 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 etcd + +import ( + "strings" + "time" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/report/factory" + "github.com/apache/dubbo-go/remoting/etcdv3" +) + +const DEFAULT_ROOT = "dubbo" + +func init() { + extension.SetMetadataReportFactory(constant.ETCDV3_KEY, func() factory.MetadataReportFactory { + return &etcdMetadataReportFactory{} + }) +} + +// etcdMetadataReport is the implementation of MetadataReport based etcd +type etcdMetadataReport struct { + client *etcdv3.Client + root string +} + +// StoreProviderMetadata will store the metadata +// metadata including the basic info of the server, provider info, and other user custom info +func (e *etcdMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error { + key := e.getNodeKey(providerIdentifier) + return e.client.Create(key, serviceDefinitions) +} + +// StoreConsumerMetadata will store the metadata +// metadata including the basic info of the server, consumer info, and other user custom info +func (e *etcdMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error { + key := e.getNodeKey(consumerMetadataIdentifier) + return e.client.Create(key, serviceParameterString) +} + +// SaveServiceMetadata will store the metadata +// metadata including the basic info of the server, service info, and other user custom info +func (e *etcdMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error { + key := e.getNodeKey(metadataIdentifier) + return e.client.Create(key, url.String()) +} + +// RemoveServiceMetadata will remove the service metadata +func (e *etcdMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error { + return e.client.Delete(e.getNodeKey(metadataIdentifier)) +} + +// GetExportedURLs will look up the exported urls. +// if not found, an empty list will be returned. +func (e *etcdMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) { + content, err := e.client.Get(e.getNodeKey(metadataIdentifier)) + if err != nil { + logger.Errorf("etcdMetadataReport GetExportedURLs err:{%v}", err.Error()) + return []string{}, err + } + if content == "" { + return []string{}, nil + } + return []string{content}, nil +} + +// SaveSubscribedData will convert the urlList to json array and then store it +func (e *etcdMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error { + key := e.getNodeKey(subscriberMetadataIdentifier) + return e.client.Create(key, urls) +} + +// GetSubscribedURLs will lookup the url +// if not found, an empty list will be returned +func (e *etcdMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { + content, err := e.client.Get(e.getNodeKey(subscriberMetadataIdentifier)) + if err != nil { + logger.Errorf("etcdMetadataReport GetSubscribedURLs err:{%v}", err.Error()) + return nil, err + } + return []string{content}, nil +} + +// GetServiceDefinition will lookup the service definition +func (e *etcdMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) { + key := e.getNodeKey(metadataIdentifier) + content, err := e.client.Get(key) + if err != nil { + logger.Errorf("etcdMetadataReport GetServiceDefinition err:{%v}", err.Error()) + return "", err + } + return content, nil +} + +type etcdMetadataReportFactory struct{} + +// CreateMetadataReport get the MetadataReport instance of etcd +func (e *etcdMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport { + timeout, _ := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + addresses := strings.Split(url.Location, ",") + client, err := etcdv3.NewClient(etcdv3.MetadataETCDV3Client, addresses, timeout, 1) + if err != nil { + logger.Errorf("Could not create etcd metadata report. URL: %s,error:{%v}", url.String(), err) + return nil + } + group := url.GetParam(constant.GROUP_KEY, DEFAULT_ROOT) + group = constant.PATH_SEPARATOR + strings.TrimPrefix(group, constant.PATH_SEPARATOR) + return &etcdMetadataReport{client: client, root: group} +} + +func (e *etcdMetadataReport) getNodeKey(MetadataIdentifier identifier.IMetadataIdentifier) string { + var rootDir string + if e.root == constant.PATH_SEPARATOR { + rootDir = e.root + } else { + rootDir = e.root + constant.PATH_SEPARATOR + } + return rootDir + MetadataIdentifier.GetFilePathKey() +} diff --git a/metadata/report/etcd/report_test.go b/metadata/report/etcd/report_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5dd8780b48a741a8eb8735b059bc3617a100f63d --- /dev/null +++ b/metadata/report/etcd/report_test.go @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package etcd + +import ( + "encoding/json" + "net/url" + "strconv" + "testing" +) + +import ( + "github.com/coreos/etcd/embed" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/metadata/identifier" +) + +const defaultEtcdV3WorkDir = "/tmp/default-dubbo-go-registry.etcd" + +func initEtcd(t *testing.T) *embed.Etcd { + DefaultListenPeerURLs := "http://localhost:2380" + DefaultListenClientURLs := "http://localhost:2379" + lpurl, _ := url.Parse(DefaultListenPeerURLs) + lcurl, _ := url.Parse(DefaultListenClientURLs) + cfg := embed.NewConfig() + cfg.LPUrls = []url.URL{*lpurl} + cfg.LCUrls = []url.URL{*lcurl} + cfg.Dir = defaultEtcdV3WorkDir + e, err := embed.StartEtcd(cfg) + if err != nil { + t.Fatal(err) + } + return e +} + +func TestEtcdMetadataReportFactory_CreateMetadataReport(t *testing.T) { + e := initEtcd(t) + url, err := common.NewURL("registry://127.0.0.1:2379", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + if err != nil { + t.Fatal(err) + } + metadataReportFactory := &etcdMetadataReportFactory{} + metadataReport := metadataReportFactory.CreateMetadataReport(&url) + assert.NotNil(t, metadataReport) + e.Close() +} + +func TestEtcdMetadataReport_CRUD(t *testing.T) { + e := initEtcd(t) + url, err := common.NewURL("registry://127.0.0.1:2379", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + if err != nil { + t.Fatal(err) + } + metadataReportFactory := &etcdMetadataReportFactory{} + metadataReport := metadataReportFactory.CreateMetadataReport(&url) + assert.NotNil(t, metadataReport) + + err = metadataReport.StoreConsumerMetadata(newMetadataIdentifier("consumer"), "consumer metadata") + assert.Nil(t, err) + + err = metadataReport.StoreProviderMetadata(newMetadataIdentifier("provider"), "provider metadata") + assert.Nil(t, err) + + serviceMi := newServiceMetadataIdentifier() + serviceUrl, _ := common.NewURL("registry://localhost:8848", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + metadataReport.SaveServiceMetadata(serviceMi, serviceUrl) + assert.Nil(t, err) + + subMi := newSubscribeMetadataIdentifier() + urlList := make([]string, 0, 1) + urlList = append(urlList, serviceUrl.String()) + urls, _ := json.Marshal(urlList) + err = metadataReport.SaveSubscribedData(subMi, string(urls)) + assert.Nil(t, err) + + err = metadataReport.RemoveServiceMetadata(serviceMi) + assert.Nil(t, err) + + e.Close() +} + +func newSubscribeMetadataIdentifier() *identifier.SubscriberMetadataIdentifier { + return &identifier.SubscriberMetadataIdentifier{ + Revision: "subscribe", + MetadataIdentifier: *newMetadataIdentifier("provider"), + } + +} + +func newServiceMetadataIdentifier() *identifier.ServiceMetadataIdentifier { + return &identifier.ServiceMetadataIdentifier{ + Protocol: "nacos", + Revision: "a", + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: "com.test.MyTest", + Version: "1.0.0", + Group: "test_group", + Side: "service", + }, + } +} + +func newMetadataIdentifier(side string) *identifier.MetadataIdentifier { + return &identifier.MetadataIdentifier{ + Application: "test", + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: "com.test.MyTest", + Version: "1.0.0", + Group: "test_group", + Side: side, + }, + } +} diff --git a/metadata/report_factory.go b/metadata/report/factory/report_factory.go similarity index 79% rename from metadata/report_factory.go rename to metadata/report/factory/report_factory.go index 19b1004eee57073acec13c7f114179c47c73f145..9f00007cefbd5737c9c53d69924eba1d556c0023 100644 --- a/metadata/report_factory.go +++ b/metadata/report/factory/report_factory.go @@ -15,16 +15,17 @@ * limitations under the License. */ -package metadata +package factory import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/metadata/report" ) -var ( - MetadataReportInstance MetadataReport -) - +// MetadataReportFactory interface will create metadata report type MetadataReportFactory interface { - CreateMetadataReport(*common.URL) MetadataReport + CreateMetadataReport(*common.URL) report.MetadataReport +} + +type BaseMetadataReportFactory struct { } diff --git a/metadata/report/nacos/report.go b/metadata/report/nacos/report.go new file mode 100644 index 0000000000000000000000000000000000000000..d69913bd8fbb04da2d50770c1196917cb1efdaa5 --- /dev/null +++ b/metadata/report/nacos/report.go @@ -0,0 +1,187 @@ +/* + * 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 ( + "net/url" +) + +import ( + "github.com/nacos-group/nacos-sdk-go/clients/config_client" + "github.com/nacos-group/nacos-sdk-go/vo" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/report/factory" + "github.com/apache/dubbo-go/remoting/nacos" +) + +func init() { + mf := &nacosMetadataReportFactory{} + extension.SetMetadataReportFactory("nacos", func() factory.MetadataReportFactory { + return mf + }) +} + +// nacosMetadataReport is the implementation +// of MetadataReport based on nacos. +type nacosMetadataReport struct { + client config_client.IConfigClient +} + +// StoreProviderMetadata stores the metadata. +func (n *nacosMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error { + return n.storeMetadata(vo.ConfigParam{ + DataId: providerIdentifier.GetIdentifierKey(), + Group: providerIdentifier.Group, + Content: serviceDefinitions, + }) +} + +// StoreConsumerMetadata stores the metadata. +func (n *nacosMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error { + return n.storeMetadata(vo.ConfigParam{ + DataId: consumerMetadataIdentifier.GetIdentifierKey(), + Group: consumerMetadataIdentifier.Group, + Content: serviceParameterString, + }) +} + +// SaveServiceMetadata saves the metadata. +func (n *nacosMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error { + return n.storeMetadata(vo.ConfigParam{ + DataId: metadataIdentifier.GetIdentifierKey(), + Group: metadataIdentifier.Group, + Content: url.String(), + }) +} + +// RemoveServiceMetadata removes the metadata. +func (n *nacosMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error { + return n.deleteMetadata(vo.ConfigParam{ + DataId: metadataIdentifier.GetIdentifierKey(), + Group: metadataIdentifier.Group, + }) +} + +// GetExportedURLs gets the urls. +func (n *nacosMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) { + return n.getConfigAsArray(vo.ConfigParam{ + DataId: metadataIdentifier.GetIdentifierKey(), + Group: metadataIdentifier.Group, + }) +} + +// SaveSubscribedData saves the urls. +func (n *nacosMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error { + return n.storeMetadata(vo.ConfigParam{ + DataId: subscriberMetadataIdentifier.GetIdentifierKey(), + Group: subscriberMetadataIdentifier.Group, + Content: urls, + }) +} + +// GetSubscribedURLs gets the urls. +func (n *nacosMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { + return n.getConfigAsArray(vo.ConfigParam{ + DataId: subscriberMetadataIdentifier.GetIdentifierKey(), + Group: subscriberMetadataIdentifier.Group, + }) +} + +// GetServiceDefinition gets the service definition. +func (n *nacosMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) { + return n.getConfig(vo.ConfigParam{ + DataId: metadataIdentifier.GetIdentifierKey(), + Group: metadataIdentifier.Group, + }) +} + +// storeMetadata will publish the metadata to Nacos +// if failed or error is not nil, error will be returned +func (n *nacosMetadataReport) storeMetadata(param vo.ConfigParam) error { + res, err := n.client.PublishConfig(param) + if err != nil { + return perrors.WithMessage(err, "Could not publish the metadata") + } + if !res { + return perrors.New("Publish the metadata failed.") + } + return nil +} + +// deleteMetadata will delete the metadata +func (n *nacosMetadataReport) deleteMetadata(param vo.ConfigParam) error { + res, err := n.client.DeleteConfig(param) + if err != nil { + return perrors.WithMessage(err, "Could not delete the metadata") + } + if !res { + return perrors.New("Deleting the metadata failed.") + } + return nil +} + +// getConfigAsArray will read the config and then convert it as an one-element array +// error or config not found, an empty list will be returned. +func (n *nacosMetadataReport) getConfigAsArray(param vo.ConfigParam) ([]string, error) { + res := make([]string, 0, 1) + + cfg, err := n.getConfig(param) + if err != nil || len(cfg) == 0 { + return res, err + } + + decodeCfg, err := url.QueryUnescape(cfg) + if err != nil { + logger.Errorf("The config is invalid: %s", cfg) + return res, err + } + + res = append(res, decodeCfg) + return res, nil +} + +// getConfig will read the config +func (n *nacosMetadataReport) getConfig(param vo.ConfigParam) (string, error) { + cfg, err := n.client.GetConfig(param) + if err != nil { + logger.Errorf("Finding the configuration failed: %v", param) + return "", err + } + return cfg, nil +} + +type nacosMetadataReportFactory struct { +} + +// nolint +func (n *nacosMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport { + client, err := nacos.NewNacosConfigClient(url) + if err != nil { + logger.Errorf("Could not create nacos metadata report. URL: %s", url.String()) + return nil + } + return &nacosMetadataReport{client: client} +} diff --git a/metadata/report/nacos/report_test.go b/metadata/report/nacos/report_test.go new file mode 100644 index 0000000000000000000000000000000000000000..be01eb22f7e95966c3bf816fdf648629b64380a3 --- /dev/null +++ b/metadata/report/nacos/report_test.go @@ -0,0 +1,116 @@ +/* + * 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 ( + "encoding/json" + "strconv" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" +) + +func TestNacosMetadataReport_CRUD(t *testing.T) { + rpt := newTestReport() + assert.NotNil(t, rpt) + + providerMi := newMetadataIdentifier("server") + providerMeta := "provider" + err := rpt.StoreProviderMetadata(providerMi, providerMeta) + assert.Nil(t, err) + + consumerMi := newMetadataIdentifier("client") + consumerMeta := "consumer" + err = rpt.StoreConsumerMetadata(consumerMi, consumerMeta) + assert.Nil(t, err) + + serviceMi := newServiceMetadataIdentifier() + serviceUrl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + err = rpt.SaveServiceMetadata(serviceMi, serviceUrl) + assert.Nil(t, err) + + exportedUrls, err := rpt.GetExportedURLs(serviceMi) + assert.Equal(t, 1, len(exportedUrls)) + assert.Nil(t, err) + + subMi := newSubscribeMetadataIdentifier() + urls := []string{serviceUrl.String()} + bytes, _ := json.Marshal(urls) + err = rpt.SaveSubscribedData(subMi, string(bytes)) + assert.Nil(t, err) + + subscribeUrl, err := rpt.GetSubscribedURLs(subMi) + assert.Equal(t, 1, len(subscribeUrl)) + assert.Nil(t, err) + + err = rpt.RemoveServiceMetadata(serviceMi) + assert.Nil(t, err) +} + +func newSubscribeMetadataIdentifier() *identifier.SubscriberMetadataIdentifier { + return &identifier.SubscriberMetadataIdentifier{ + Revision: "subscribe", + MetadataIdentifier: *newMetadataIdentifier("provider"), + } +} + +func newServiceMetadataIdentifier() *identifier.ServiceMetadataIdentifier { + return &identifier.ServiceMetadataIdentifier{ + Protocol: "nacos", + Revision: "a", + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: "com.test.MyTest", + Version: "1.0.0", + Group: "test_group", + Side: "service", + }, + } +} + +func newMetadataIdentifier(side string) *identifier.MetadataIdentifier { + return &identifier.MetadataIdentifier{ + Application: "test", + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: "com.test.MyTest", + Version: "1.0.0", + Group: "test_group", + Side: side, + }, + } +} + +func TestNacosMetadataReportFactory_CreateMetadataReport(t *testing.T) { + res := newTestReport() + assert.NotNil(t, res) +} + +func newTestReport() report.MetadataReport { + regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + res := extension.GetMetadataReportFactory("nacos").CreateMetadataReport(®url) + return res +} diff --git a/metadata/report/report.go b/metadata/report/report.go new file mode 100644 index 0000000000000000000000000000000000000000..62a9055e843297bd0d69ad94cb09ece64efda85f --- /dev/null +++ b/metadata/report/report.go @@ -0,0 +1,60 @@ +/* + * 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 report + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/metadata/identifier" +) + +// MetadataReport is an interface of +// remote metadata report. +type MetadataReport interface { + // StoreProviderMetadata stores the metadata. + // Metadata includes the basic info of the server, + // provider info, and other user custom info. + StoreProviderMetadata(*identifier.MetadataIdentifier, string) error + + // StoreConsumerMetadata stores the metadata. + // Metadata includes the basic info of the server, + // consumer info, and other user custom info. + StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error + + // SaveServiceMetadata saves the metadata. + // Metadata includes the basic info of the server, + // service info, and other user custom info. + SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error + + // RemoveServiceMetadata removes the metadata. + RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error + + // GetExportedURLs gets the urls. + // If not found, an empty list will be returned. + GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error) + + // SaveSubscribedData saves the urls. + // If not found, an empty str will be returned. + SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, string) error + + // GetSubscribedURLs gets the urls. + // If not found, an empty list will be returned. + GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error) + + // GetServiceDefinition gets the service definition. + GetServiceDefinition(*identifier.MetadataIdentifier) (string, error) +} diff --git a/metadata/report/zookeeper/report.go b/metadata/report/zookeeper/report.go new file mode 100644 index 0000000000000000000000000000000000000000..8f46bb023054ec27ca0dbff2c249dc0135af07cd --- /dev/null +++ b/metadata/report/zookeeper/report.go @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package zookeeper + +import ( + "strings" + "time" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/report/factory" + "github.com/apache/dubbo-go/remoting/zookeeper" +) + +var ( + emptyStrSlice = make([]string, 0) +) + +func init() { + mf := &zookeeperMetadataReportFactory{} + extension.SetMetadataReportFactory("zookeeper", func() factory.MetadataReportFactory { + return mf + }) +} + +// zookeeperMetadataReport is the implementation of +// MetadataReport based on zookeeper. +type zookeeperMetadataReport struct { + client *zookeeper.ZookeeperClient + rootDir string +} + +// StoreProviderMetadata stores the metadata. +func (m *zookeeperMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error { + k := m.rootDir + providerIdentifier.GetFilePathKey() + return m.client.CreateWithValue(k, []byte(serviceDefinitions)) +} + +// StoreConsumerMetadata stores the metadata. +func (m *zookeeperMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error { + k := m.rootDir + consumerMetadataIdentifier.GetFilePathKey() + return m.client.CreateWithValue(k, []byte(serviceParameterString)) +} + +// SaveServiceMetadata saves the metadata. +func (m *zookeeperMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error { + k := m.rootDir + metadataIdentifier.GetFilePathKey() + return m.client.CreateWithValue(k, []byte(url.String())) +} + +// RemoveServiceMetadata removes the metadata. +func (m *zookeeperMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error { + k := m.rootDir + metadataIdentifier.GetFilePathKey() + return m.client.Delete(k) +} + +// GetExportedURLs gets the urls. +func (m *zookeeperMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) ([]string, error) { + k := m.rootDir + metadataIdentifier.GetFilePathKey() + v, _, err := m.client.GetContent(k) + if err != nil || len(v) == 0 { + return emptyStrSlice, err + } + return []string{string(v)}, nil +} + +// SaveSubscribedData saves the urls. +func (m *zookeeperMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urls string) error { + k := m.rootDir + subscriberMetadataIdentifier.GetFilePathKey() + return m.client.CreateWithValue(k, []byte(urls)) +} + +// GetSubscribedURLs gets the urls. +func (m *zookeeperMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { + k := m.rootDir + subscriberMetadataIdentifier.GetFilePathKey() + v, _, err := m.client.GetContent(k) + if err != nil || len(v) == 0 { + return emptyStrSlice, err + } + return []string{string(v)}, nil +} + +// GetServiceDefinition gets the service definition. +func (m *zookeeperMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) (string, error) { + k := m.rootDir + metadataIdentifier.GetFilePathKey() + v, _, err := m.client.GetContent(k) + return string(v), err +} + +type zookeeperMetadataReportFactory struct { +} + +// nolint +func (mf *zookeeperMetadataReportFactory) CreateMetadataReport(url *common.URL) report.MetadataReport { + client, err := zookeeper.NewZookeeperClient( + "zookeeperMetadataReport", + strings.Split(url.Location, ","), + 15*time.Second, + ) + if err != nil { + panic(err) + } + + rootDir := url.GetParam(constant.GROUP_KEY, "dubbo") + if !strings.HasPrefix(rootDir, constant.PATH_SEPARATOR) { + rootDir = constant.PATH_SEPARATOR + rootDir + } + if rootDir != constant.PATH_SEPARATOR { + rootDir = rootDir + constant.PATH_SEPARATOR + } + + return &zookeeperMetadataReport{client: client, rootDir: rootDir} +} diff --git a/metadata/report/zookeeper/report_test.go b/metadata/report/zookeeper/report_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a1e46e2e8d019c0415699ee409833b392a85b504 --- /dev/null +++ b/metadata/report/zookeeper/report_test.go @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package zookeeper + +import ( + "encoding/json" + "net/url" + "strconv" + "testing" +) + +import ( + "github.com/dubbogo/go-zookeeper/zk" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" +) + +func newProviderRegistryUrl(host string, port int) *common.URL { + return common.NewURLWithOptions( + common.WithIp(host), + common.WithPort(strconv.Itoa(port)), + common.WithParams(url.Values{}), + common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)), + ) +} + +func newBaseMetadataIdentifier(side string) *identifier.BaseMetadataIdentifier { + return &identifier.BaseMetadataIdentifier{ + ServiceInterface: "org.apache.HelloWorld", + Version: "1.0.0", + Group: "group", + Side: side, + } +} + +func newMetadataIdentifier(side string) *identifier.MetadataIdentifier { + return &identifier.MetadataIdentifier{ + Application: "application", + BaseMetadataIdentifier: *newBaseMetadataIdentifier(side), + } +} + +func newServiceMetadataIdentifier(side string) *identifier.ServiceMetadataIdentifier { + return &identifier.ServiceMetadataIdentifier{ + Revision: "1.0", + Protocol: "dubbo", + BaseMetadataIdentifier: *newBaseMetadataIdentifier(side), + } +} + +func newSubscribeMetadataIdentifier(side string) *identifier.SubscriberMetadataIdentifier { + return &identifier.SubscriberMetadataIdentifier{ + Revision: "1.0", + MetadataIdentifier: *newMetadataIdentifier(side), + } +} + +type zookeeperMetadataReportTestSuite struct { + t *testing.T + m report.MetadataReport +} + +func newZookeeperMetadataReportTestSuite(t *testing.T, m report.MetadataReport) *zookeeperMetadataReportTestSuite { + return &zookeeperMetadataReportTestSuite{t: t, m: m} +} + +func (suite *zookeeperMetadataReportTestSuite) testStoreProviderMetadata() { + providerMi := newMetadataIdentifier("provider") + providerMeta := "provider" + err := suite.m.StoreProviderMetadata(providerMi, providerMeta) + assert.NoError(suite.t, err) +} + +func (suite *zookeeperMetadataReportTestSuite) testStoreConsumerMetadata() { + consumerMi := newMetadataIdentifier("consumer") + consumerMeta := "consumer" + err := suite.m.StoreProviderMetadata(consumerMi, consumerMeta) + assert.NoError(suite.t, err) +} + +func (suite *zookeeperMetadataReportTestSuite) testSaveServiceMetadata(url common.URL) { + serviceMi := newServiceMetadataIdentifier("provider") + err := suite.m.SaveServiceMetadata(serviceMi, url) + assert.NoError(suite.t, err) +} + +func (suite *zookeeperMetadataReportTestSuite) testRemoveServiceMetadata() { + serviceMi := newServiceMetadataIdentifier("provider") + err := suite.m.RemoveServiceMetadata(serviceMi) + assert.NoError(suite.t, err) +} + +func (suite *zookeeperMetadataReportTestSuite) testGetExportedURLs() { + serviceMi := newServiceMetadataIdentifier("provider") + urls, err := suite.m.GetExportedURLs(serviceMi) + assert.Equal(suite.t, 1, len(urls)) + assert.NoError(suite.t, err) +} + +func (suite *zookeeperMetadataReportTestSuite) testSaveSubscribedData(url common.URL) { + subscribeMi := newSubscribeMetadataIdentifier("provider") + urls := []string{url.String()} + bytes, _ := json.Marshal(urls) + err := suite.m.SaveSubscribedData(subscribeMi, string(bytes)) + assert.Nil(suite.t, err) +} + +func (suite *zookeeperMetadataReportTestSuite) testGetSubscribedURLs() { + subscribeMi := newSubscribeMetadataIdentifier("provider") + urls, err := suite.m.GetSubscribedURLs(subscribeMi) + assert.Equal(suite.t, 1, len(urls)) + assert.NoError(suite.t, err) +} + +func (suite *zookeeperMetadataReportTestSuite) testGetServiceDefinition() { + providerMi := newMetadataIdentifier("provider") + providerMeta, err := suite.m.GetServiceDefinition(providerMi) + assert.Equal(suite.t, "provider", providerMeta) + assert.NoError(suite.t, err) +} + +func test1(t *testing.T) { + testCluster, err := zk.StartTestCluster(1, nil, nil) + assert.NoError(t, err) + defer testCluster.Stop() + + url := newProviderRegistryUrl("127.0.0.1", testCluster.Servers[0].Port) + mf := extension.GetMetadataReportFactory("zookeeper") + m := mf.CreateMetadataReport(url) + + suite := newZookeeperMetadataReportTestSuite(t, m) + suite.testStoreProviderMetadata() + suite.testStoreConsumerMetadata() + suite.testSaveServiceMetadata(*url) + suite.testGetExportedURLs() + suite.testRemoveServiceMetadata() + suite.testSaveSubscribedData(*url) + suite.testGetSubscribedURLs() + suite.testGetServiceDefinition() +} + +func TestZookeeperMetadataReport(t *testing.T) { + t.Run("test1", test1) +} diff --git a/metadata/service.go b/metadata/service.go deleted file mode 100644 index 89df68fb313b1abe63082c0c220b0114c11fca19..0000000000000000000000000000000000000000 --- a/metadata/service.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 metadata - -import ( - "github.com/apache/dubbo-go/common" - gxset "github.com/dubbogo/gost/container/set" -) - -// Metadata service is a built-in service around the metadata of Dubbo services, -// whose interface is provided by Dubbo Framework and exported automatically before subscription after other services exporting, -// which may be used for Dubbo subscribers and admin. -type MetadataService interface { - ServiceName() string - ExportURL(url *common.URL) bool - UnexportURL(url *common.URL) bool - RefreshMetadata(exportedRevision string, subscribedRevision string) bool - SubscribeURL(url *common.URL) bool - UnsubscribeURL(url *common.URL) bool - PublishServiceDefinition(url *common.URL) - - GetExportedURLs(serviceInterface string, group string, version string, protocol string) gxset.HashSet - GetServiceDefinition(interfaceName string, version string, group string) string - GetServiceDefinitionByServiceKey(serviceKey string) string -} diff --git a/metadata/service/exporter/configurable/exporter.go b/metadata/service/exporter/configurable/exporter.go new file mode 100644 index 0000000000000000000000000000000000000000..f8b4b0c0174cb0e5a8753b814f89ed4d332e2fbe --- /dev/null +++ b/metadata/service/exporter/configurable/exporter.go @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package configurable + +import ( + "context" + "sync" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/metadata/service/exporter" +) + +// MetadataServiceExporter is the ConfigurableMetadataServiceExporter which implement MetadataServiceExporter interface +type MetadataServiceExporter struct { + ServiceConfig *config.ServiceConfig + lock sync.RWMutex + metadataService service.MetadataService +} + +// NewMetadataServiceExporter will return a service_exporter.MetadataServiceExporter with the specified metadata service +func NewMetadataServiceExporter(metadataService service.MetadataService) exporter.MetadataServiceExporter { + return &MetadataServiceExporter{ + metadataService: metadataService, + } +} + +// Export will export the metadataService +func (exporter *MetadataServiceExporter) Export() error { + if !exporter.IsExported() { + + serviceConfig := config.NewServiceConfig(constant.SIMPLE_METADATA_SERVICE_NAME, context.Background()) + serviceConfig.Protocol = constant.DEFAULT_PROTOCOL + serviceConfig.Protocols = map[string]*config.ProtocolConfig{ + constant.DEFAULT_PROTOCOL: generateMetadataProtocol(), + } + serviceConfig.InterfaceName = constant.METADATA_SERVICE_NAME + // identify this is a golang server + serviceConfig.Params = map[string]string{} + serviceConfig.Group = config.GetApplicationConfig().Name + // now the error will always be nil + serviceConfig.Version, _ = exporter.metadataService.Version() + + var err error + func() { + exporter.lock.Lock() + defer exporter.lock.Unlock() + exporter.ServiceConfig = serviceConfig + exporter.ServiceConfig.Implement(exporter.metadataService) + err = exporter.ServiceConfig.Export() + }() + + logger.Infof("The MetadataService exports urls : %v ", exporter.ServiceConfig.GetExportedUrls()) + return err + } + logger.Warnf("The MetadataService has been exported : %v ", exporter.ServiceConfig.GetExportedUrls()) + return nil +} + +// Unexport will unexport the metadataService +func (exporter *MetadataServiceExporter) Unexport() { + if exporter.IsExported() { + exporter.ServiceConfig.Unexport() + } +} + +// GetExportedURLs will return the urls that export use. +// Notice锛乀he exported url is not same as url in registry , for example it lack the ip. +func (exporter *MetadataServiceExporter) GetExportedURLs() []*common.URL { + return exporter.ServiceConfig.GetExportedUrls() +} + +// isExported will return is metadataServiceExporter exported or not +func (exporter *MetadataServiceExporter) IsExported() bool { + exporter.lock.RLock() + defer exporter.lock.RUnlock() + return exporter.ServiceConfig != nil && exporter.ServiceConfig.IsExport() +} + +// generateMetadataProtocol will return a default ProtocolConfig +func generateMetadataProtocol() *config.ProtocolConfig { + return &config.ProtocolConfig{ + Name: constant.DEFAULT_PROTOCOL, + Port: "20000", + } +} diff --git a/metadata/service/exporter/configurable/exporter_test.go b/metadata/service/exporter/configurable/exporter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b85e0ac2ac9378d3d9e2572e45255f37f5f84eab --- /dev/null +++ b/metadata/service/exporter/configurable/exporter_test.go @@ -0,0 +1,122 @@ +/* + * 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 configurable + +import ( + "github.com/apache/dubbo-go/remoting/getty" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + _ "github.com/apache/dubbo-go/common/proxy/proxy_factory" + "github.com/apache/dubbo-go/config" + _ "github.com/apache/dubbo-go/filter/filter_impl" + "github.com/apache/dubbo-go/metadata/service/inmemory" + _ "github.com/apache/dubbo-go/protocol/dubbo" +) + +func TestConfigurableExporter(t *testing.T) { + getty.SetServerConfig(getty.ServerConfig{ + SessionNumber: 700, + SessionTimeout: "20s", + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "1s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "server", + }}) + mockInitProviderWithSingleRegistry() + metadataService, _ := inmemory.NewMetadataService() + exported := NewMetadataServiceExporter(metadataService) + assert.Equal(t, false, exported.IsExported()) + assert.NoError(t, exported.Export()) + assert.Equal(t, true, exported.IsExported()) + assert.Regexp(t, "dubbo://:20000/MetadataService*", exported.GetExportedURLs()[0].String()) + exported.Unexport() + assert.Equal(t, false, exported.IsExported()) +} + +// mockInitProviderWithSingleRegistry will init a mocked providerConfig +func mockInitProviderWithSingleRegistry() { + providerConfig := &config.ProviderConfig{ + + BaseConfig: config.BaseConfig{ + ApplicationConfig: &config.ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "1.0.0", + Owner: "dubbo", + Environment: "test"}, + }, + + Registry: &config.RegistryConfig{ + Address: "mock://127.0.0.1:2181", + Username: "user1", + Password: "pwd1", + }, + Registries: map[string]*config.RegistryConfig{}, + + Services: map[string]*config.ServiceConfig{ + "MockService": { + InterfaceName: "com.MockService", + Protocol: "mock", + Cluster: "failover", + Loadbalance: "random", + Retries: "3", + Group: "huadong_idc", + Version: "1.0.0", + Methods: []*config.MethodConfig{ + { + Name: "GetUser", + Retries: "2", + LoadBalance: "random", + Weight: 200, + }, + { + Name: "GetUser1", + Retries: "2", + LoadBalance: "random", + Weight: 200, + }, + }, + }, + }, + Protocols: map[string]*config.ProtocolConfig{ + "mock": { + Name: "mock", + Ip: "127.0.0.1", + Port: "20000", + }, + }, + } + providerConfig.Services["MockService"].InitExported() + config.SetProviderConfig(*providerConfig) +} diff --git a/metadata/exporter.go b/metadata/service/exporter/exporter.go similarity index 81% rename from metadata/exporter.go rename to metadata/service/exporter/exporter.go index 5d47f8bd808ec802ba73c7db73d22c78c675d12a..cfdef3a0e79d29ce31717c0fc3c575e9e4ba1759 100644 --- a/metadata/exporter.go +++ b/metadata/service/exporter/exporter.go @@ -15,15 +15,16 @@ * limitations under the License. */ -package metadata +package exporter import ( "github.com/apache/dubbo-go/common" ) -type MetadataExporter interface { - Export() MetadataExporter - Unexport() MetadataExporter +// MetadataServiceExporter will export & unexport the metadata service, get exported url, and return is exported or not +type MetadataServiceExporter interface { + Export() error + Unexport() GetExportedURLs() []*common.URL IsExported() bool } diff --git a/metadata/service/inmemory/metadata_service_proxy_factory.go b/metadata/service/inmemory/metadata_service_proxy_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..1f8eeaa55f4a0240746508fee2ff088e3a653ca5 --- /dev/null +++ b/metadata/service/inmemory/metadata_service_proxy_factory.go @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package inmemory + +import ( + "encoding/json" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" +) + +func init() { + factory := service.NewBaseMetadataServiceProxyFactory(createProxy) + extension.SetMetadataServiceProxyFactory(local, func() service.MetadataServiceProxyFactory { + return factory + }) +} + +// createProxy creates an instance of MetadataServiceProxy +// we read the metadata from ins.Metadata() +// and then create an Invoker instance +// also we will mark this proxy as golang's proxy +func createProxy(ins registry.ServiceInstance) service.MetadataService { + urls := buildStandardMetadataServiceURL(ins) + if len(urls) == 0 { + logger.Errorf("metadata service urls not found, %v", ins) + return nil + } + + u := urls[0] + p := extension.GetProtocol(u.Protocol) + invoker := p.Refer(*u) + return &MetadataServiceProxy{ + invkr: invoker, + } +} + +// buildStandardMetadataServiceURL will use standard format to build the metadata service url. +func buildStandardMetadataServiceURL(ins registry.ServiceInstance) []*common.URL { + ps := getMetadataServiceUrlParams(ins) + res := make([]*common.URL, 0, len(ps)) + sn := ins.GetServiceName() + host := ins.GetHost() + for protocol, params := range ps { + + convertedParams := make(map[string][]string, len(params)) + for k, v := range params { + convertedParams[k] = []string{v} + } + + u := common.NewURLWithOptions(common.WithIp(host), + common.WithPath(constant.METADATA_SERVICE_NAME), + common.WithProtocol(protocol), + common.WithPort(params[constant.PORT_KEY]), + common.WithParams(convertedParams), + common.WithParamsValue(constant.GROUP_KEY, sn)) + res = append(res, u) + } + return res +} + +// getMetadataServiceUrlParams this will convert the metadata service url parameters to map structure +// it looks like: +// {"dubbo":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}} +func getMetadataServiceUrlParams(ins registry.ServiceInstance) map[string]map[string]string { + ps := ins.GetMetadata() + res := make(map[string]map[string]string, 2) + if str, ok := ps[constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME]; ok && len(str) > 0 { + + err := json.Unmarshal([]byte(str), &res) + if err != nil { + logger.Errorf("could not parse the metadata service url parameters to map", err) + } + } + return res +} diff --git a/metadata/service/inmemory/metadata_service_proxy_factory_test.go b/metadata/service/inmemory/metadata_service_proxy_factory_test.go new file mode 100644 index 0000000000000000000000000000000000000000..96020e1eb762442f946ccf8b368d6ebe9429d05e --- /dev/null +++ b/metadata/service/inmemory/metadata_service_proxy_factory_test.go @@ -0,0 +1,100 @@ +/* + * 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 inmemory + +import ( + "context" + "encoding/json" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/registry" +) + +func TestMetadataService_GetMetadataServiceUrlParams(t *testing.T) { + str := `{"dubbo":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}` + tmp := make(map[string]map[string]string) + err := json.Unmarshal([]byte(str), &tmp) + assert.Nil(t, err) +} + +func TestCreateProxy(t *testing.T) { + extension.SetProtocol("mock", func() protocol.Protocol { + return &mockProtocol{} + }) + ins := ®istry.DefaultServiceInstance{ + Id: "test-id", + ServiceName: "com.dubbo", + Host: "localhost", + Port: 8080, + Enable: true, + Healthy: true, + } + + pxy := createProxy(ins) + assert.Nil(t, pxy) + + ins.Metadata = map[string]string{constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME: `{"mock":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}`} + pxy = createProxy(ins) + assert.NotNil(t, pxy) +} + +type mockProtocol struct { +} + +func (m mockProtocol) Export(invoker protocol.Invoker) protocol.Exporter { + panic("implement me") +} + +func (m mockProtocol) Refer(url common.URL) protocol.Invoker { + return &mockInvoker{} +} + +func (m mockProtocol) Destroy() { + panic("implement me") +} + +type mockInvoker struct { +} + +func (m *mockInvoker) GetUrl() common.URL { + panic("implement me") +} + +func (m *mockInvoker) IsAvailable() bool { + panic("implement me") +} + +func (m *mockInvoker) Destroy() { + panic("implement me") +} + +func (m *mockInvoker) Invoke(context.Context, protocol.Invocation) protocol.Result { + return &protocol.RPCResult{ + Rest: &[]interface{}{"dubbo://localhost"}, + } +} diff --git a/metadata/service/inmemory/service.go b/metadata/service/inmemory/service.go new file mode 100644 index 0000000000000000000000000000000000000000..8269e691f1794fd9ac4b6091c157539e39ad7072 --- /dev/null +++ b/metadata/service/inmemory/service.go @@ -0,0 +1,249 @@ +/* + * 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 inmemory + +import ( + "sort" + "sync" +) + +import ( + cm "github.com/Workiva/go-datastructures/common" + "github.com/Workiva/go-datastructures/slice/skip" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/definition" + "github.com/apache/dubbo-go/metadata/service" +) + +// version will be used by Version func +const ( + version = "1.0.0" + local = "local" +) + +func init() { + extension.SetMetadataService(local, NewMetadataService) +} + +// MetadataService is store and query the metadata info in memory when each service registry +type MetadataService struct { + service.BaseMetadataService + exportedServiceURLs *sync.Map + subscribedServiceURLs *sync.Map + serviceDefinitions *sync.Map + lock *sync.RWMutex +} + +var ( + metadataServiceInstance *MetadataService + metadataServiceInitOnce sync.Once +) + +// NewMetadataService: initiate a metadata service +// it should be singleton +func NewMetadataService() (service.MetadataService, error) { + metadataServiceInitOnce.Do(func() { + metadataServiceInstance = &MetadataService{ + BaseMetadataService: service.NewBaseMetadataService(config.GetApplicationConfig().Name), + exportedServiceURLs: &sync.Map{}, + subscribedServiceURLs: &sync.Map{}, + serviceDefinitions: &sync.Map{}, + lock: &sync.RWMutex{}, + } + }) + return metadataServiceInstance, nil +} + +// Comparator is defined as Comparator for skip list to compare the URL +type Comparator common.URL + +// Compare is defined as Comparator for skip list to compare the URL +func (c Comparator) Compare(comp cm.Comparator) int { + a := common.URL(c).String() + b := common.URL(comp.(Comparator)).String() + switch { + case a > b: + return 1 + case a < b: + return -1 + default: + return 0 + } +} + +// addURL will add URL in memory +func (mts *MetadataService) addURL(targetMap *sync.Map, url *common.URL) bool { + var ( + urlSet interface{} + loaded bool + ) + logger.Debug(url.ServiceKey()) + if urlSet, loaded = targetMap.LoadOrStore(url.ServiceKey(), skip.New(uint64(0))); loaded { + mts.lock.RLock() + wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + if len(wantedUrl) > 0 && wantedUrl[0] != nil { + mts.lock.RUnlock() + return false + } + mts.lock.RUnlock() + } + mts.lock.Lock() + // double chk + wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + if len(wantedUrl) > 0 && wantedUrl[0] != nil { + mts.lock.Unlock() + return false + } + urlSet.(*skip.SkipList).Insert(Comparator(*url)) + mts.lock.Unlock() + return true +} + +// removeURL is used to remove specified url +func (mts *MetadataService) removeURL(targetMap *sync.Map, url *common.URL) { + if value, loaded := targetMap.Load(url.ServiceKey()); loaded { + mts.lock.Lock() + value.(*skip.SkipList).Delete(Comparator(*url)) + mts.lock.Unlock() + mts.lock.RLock() + defer mts.lock.RUnlock() + if value.(*skip.SkipList).Len() == 0 { + targetMap.Delete(url.ServiceKey()) + } + } +} + +// getAllService can return all the exportedUrlString except for metadataService +func (mts *MetadataService) getAllService(services *sync.Map) []common.URL { + // using skip list to dedup and sorting + res := make([]common.URL, 0) + services.Range(func(key, value interface{}) bool { + urls := value.(*skip.SkipList) + for i := uint64(0); i < urls.Len(); i++ { + url := common.URL(urls.ByPosition(i).(Comparator)) + if url.GetParam(constant.INTERFACE_KEY, url.Path) != constant.METADATA_SERVICE_NAME { + res = append(res, url) + } + } + return true + }) + sort.Sort(common.URLSlice(res)) + return res +} + +// getSpecifiedService can return specified service url by serviceKey +func (mts *MetadataService) getSpecifiedService(services *sync.Map, serviceKey string, protocol string) []common.URL { + res := make([]common.URL, 0) + serviceList, loaded := services.Load(serviceKey) + if loaded { + urls := serviceList.(*skip.SkipList) + for i := uint64(0); i < urls.Len(); i++ { + url := common.URL(urls.ByPosition(i).(Comparator)) + if len(protocol) == 0 || protocol == constant.ANY_VALUE || url.Protocol == protocol || url.GetParam(constant.PROTOCOL_KEY, "") == protocol { + res = append(res, url) + } + } + sort.Stable(common.URLSlice(res)) + } + return res +} + +// ExportURL can store the in memory +func (mts *MetadataService) ExportURL(url common.URL) (bool, error) { + return mts.addURL(mts.exportedServiceURLs, &url), nil +} + +// UnexportURL can remove the url store in memory +func (mts *MetadataService) UnexportURL(url common.URL) error { + mts.removeURL(mts.exportedServiceURLs, &url) + return nil +} + +// SubscribeURL can store the in memory +func (mts *MetadataService) SubscribeURL(url common.URL) (bool, error) { + return mts.addURL(mts.subscribedServiceURLs, &url), nil +} + +// UnsubscribeURL can remove the url store in memory +func (mts *MetadataService) UnsubscribeURL(url common.URL) error { + mts.removeURL(mts.subscribedServiceURLs, &url) + return nil +} + +// PublishServiceDefinition: publish url's service metadata info, and write into memory +func (mts *MetadataService) PublishServiceDefinition(url common.URL) error { + interfaceName := url.GetParam(constant.INTERFACE_KEY, "") + isGeneric := url.GetParamBool(constant.GENERIC_KEY, false) + if len(interfaceName) > 0 && !isGeneric { + service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) + sd := definition.BuildServiceDefinition(*service, url) + data, err := sd.ToBytes() + if err != nil { + logger.Errorf("publishProvider getServiceDescriptor error. providerUrl:%v , error:%v ", url, err) + return nil + } + mts.serviceDefinitions.Store(url.ServiceKey(), string(data)) + return nil + } + logger.Errorf("publishProvider interfaceName is empty . providerUrl:%v ", url) + return nil +} + +// GetExportedURLs get all exported urls +func (mts *MetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + if serviceInterface == constant.ANY_VALUE { + return service.ConvertURLArrToIntfArr(mts.getAllService(mts.exportedServiceURLs)), nil + } else { + serviceKey := definition.ServiceDescriperBuild(serviceInterface, group, version) + return service.ConvertURLArrToIntfArr(mts.getSpecifiedService(mts.exportedServiceURLs, serviceKey, protocol)), nil + } +} + +// GetSubscribedURLs get all subscribedUrl +func (mts *MetadataService) GetSubscribedURLs() ([]common.URL, error) { + return mts.getAllService(mts.subscribedServiceURLs), nil +} + +// GetServiceDefinition can get service definition by interfaceName, group and version +func (mts *MetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + serviceKey := definition.ServiceDescriperBuild(interfaceName, group, version) + v, _ := mts.serviceDefinitions.Load(serviceKey) + return v.(string), nil +} + +// GetServiceDefinition can get service definition by serviceKey +func (mts *MetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + v, _ := mts.serviceDefinitions.Load(serviceKey) + return v.(string), nil +} + +// RefreshMetadata will always return true because it will be implement by remote service +func (mts *MetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + return true, nil +} + +// Version will return the version of metadata service +func (mts *MetadataService) Version() (string, error) { + return version, nil +} diff --git a/metadata/service/inmemory/service_proxy.go b/metadata/service/inmemory/service_proxy.go new file mode 100644 index 0000000000000000000000000000000000000000..7e01439f042a2046559188ec9df6924da0236cb1 --- /dev/null +++ b/metadata/service/inmemory/service_proxy.go @@ -0,0 +1,139 @@ +/* + * 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 inmemory + +import ( + "context" + "reflect" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +// actually it's RPC stub +// this will only be used by client-side +// if the metadata service is "local" metadata service in server side, +// which means that metadata service is RPC service too. +// so in client-side, if we want to get the metadata information, +// we must call metadata service +// this is the stub, or proxy +// for now, only GetExportedURLs need to be implemented +type MetadataServiceProxy struct { + invkr protocol.Invoker + golangServer bool +} + +func (m *MetadataServiceProxy) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + + siV := reflect.ValueOf(serviceInterface) + gV := reflect.ValueOf(group) + vV := reflect.ValueOf(version) + pV := reflect.ValueOf(protocol) + + const methodName = "getExportedURLs" + + inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(methodName), + invocation.WithArguments([]interface{}{siV.Interface(), gV.Interface(), vV.Interface(), pV.Interface()}), + invocation.WithReply(reflect.ValueOf(&[]interface{}{}).Interface()), + invocation.WithAttachments(map[string]string{constant.ASYNC_KEY: "false"}), + invocation.WithParameterValues([]reflect.Value{siV, gV, vV, pV})) + + res := m.invkr.Invoke(context.Background(), inv) + if res.Error() != nil { + logger.Errorf("could not get the metadata service from remote provider: %v", res.Error()) + return []interface{}{}, nil + } + + urlStrs := res.Result().(*[]interface{}) + + ret := make([]interface{}, 0, len(*urlStrs)) + + for _, s := range *urlStrs { + ret = append(ret, s) + } + return ret, nil +} + +func (m *MetadataServiceProxy) MethodMapper() map[string]string { + return map[string]string{} +} + +func (m *MetadataServiceProxy) Reference() string { + logger.Error("you should never invoke this implementation") + return constant.METADATA_SERVICE_NAME +} + +func (m *MetadataServiceProxy) ServiceName() (string, error) { + logger.Error("you should never invoke this implementation") + return "", nil +} + +func (m *MetadataServiceProxy) ExportURL(url common.URL) (bool, error) { + logger.Error("you should never invoke this implementation") + return false, nil +} + +func (m *MetadataServiceProxy) UnexportURL(url common.URL) error { + logger.Error("you should never invoke this implementation") + return nil +} + +func (m *MetadataServiceProxy) SubscribeURL(url common.URL) (bool, error) { + logger.Error("you should never invoke this implementation") + return false, nil +} + +func (m *MetadataServiceProxy) UnsubscribeURL(url common.URL) error { + logger.Error("you should never invoke this implementation") + return nil +} + +func (m *MetadataServiceProxy) PublishServiceDefinition(url common.URL) error { + logger.Error("you should never invoke this implementation") + return nil +} + +func (m *MetadataServiceProxy) GetSubscribedURLs() ([]common.URL, error) { + logger.Error("you should never invoke this implementation") + return []common.URL{}, nil +} + +func (m *MetadataServiceProxy) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + logger.Error("you should never invoke this implementation") + return "", nil +} + +func (m *MetadataServiceProxy) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + logger.Error("you should never invoke this implementation") + return "", nil +} + +func (m *MetadataServiceProxy) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + logger.Error("you should never invoke this implementation") + return false, nil +} + +func (m *MetadataServiceProxy) Version() (string, error) { + logger.Error("you should never invoke this implementation") + return "", nil +} diff --git a/metadata/service/inmemory/service_proxy_test.go b/metadata/service/inmemory/service_proxy_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0d75517e418133ffbf3804ec96f061dda09b9e5e --- /dev/null +++ b/metadata/service/inmemory/service_proxy_test.go @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package inmemory + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/registry" +) + +func TestMetadataServiceProxy_GetExportedURLs(t *testing.T) { + + pxy := createPxy() + assert.NotNil(t, pxy) + res, err := pxy.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + assert.Nil(t, err) + assert.Len(t, res, 1) + +} + +// TestNewMetadataService: those methods are not implemented +// when we implement them, adding UT +func TestNewMetadataService(t *testing.T) { + pxy := createPxy() + pxy.ServiceName() + pxy.PublishServiceDefinition(common.URL{}) + pxy.GetServiceDefinition(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + pxy.Version() + pxy.GetSubscribedURLs() + pxy.UnsubscribeURL(common.URL{}) + pxy.GetServiceDefinitionByServiceKey("any") + pxy.ExportURL(common.URL{}) + pxy.SubscribeURL(common.URL{}) + pxy.MethodMapper() + pxy.UnexportURL(common.URL{}) + pxy.RefreshMetadata(constant.ANY_VALUE, constant.ANY_VALUE) + +} + +func createPxy() service.MetadataService { + extension.SetProtocol("mock", func() protocol.Protocol { + return &mockProtocol{} + }) + + ins := ®istry.DefaultServiceInstance{ + Id: "test-id", + ServiceName: "com.dubbo", + Host: "localhost", + Port: 8080, + Enable: true, + Healthy: true, + Metadata: map[string]string{constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME: `{"mock":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}`}, + } + + return extension.GetMetadataServiceProxyFactory(local).GetProxy(ins) +} diff --git a/metadata/service/inmemory/service_test.go b/metadata/service/inmemory/service_test.go new file mode 100644 index 0000000000000000000000000000000000000000..048c286fdf28fba6a15a86164df0789d421f0797 --- /dev/null +++ b/metadata/service/inmemory/service_test.go @@ -0,0 +1,95 @@ +/* + * 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 inmemory + +import ( + "fmt" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/metadata/definition" +) + +func TestMetadataService(t *testing.T) { + mts, _ := NewMetadataService() + serviceName := "com.ikurento.user.UserProvider" + group := "group1" + version := "0.0.1" + protocol := "dubbo" + beanName := "UserProvider" + + u2, err := common.NewURL(fmt.Sprintf( + "%v://127.0.0.1:20000/com.ikurento.user.UserProvider2?anyhost=true&"+ + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ + "environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+ + "owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245&group=%v&version=%v&bean.name=%v", + protocol, serviceName, group, version, beanName)) + assert.NoError(t, err) + mts.ExportURL(u2) + + u3, err := common.NewURL(fmt.Sprintf( + "%v://127.0.0.1:20000/com.ikurento.user.UserProvider3?anyhost=true&"+ + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ + "environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+ + "owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245&group=%v&version=%v&bean.name=%v", + protocol, serviceName, group, version, beanName)) + assert.NoError(t, err) + mts.ExportURL(u3) + + u, err := common.NewURL(fmt.Sprintf( + "%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+ + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ + "environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+ + "owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245&group=%v&version=%v&bean.name=%v", + protocol, serviceName, group, version, beanName)) + assert.NoError(t, err) + mts.ExportURL(u) + list, _ := mts.GetExportedURLs(serviceName, group, version, protocol) + assert.Equal(t, 3, len(list)) + mts.SubscribeURL(u) + + mts.SubscribeURL(u) + list2, _ := mts.GetSubscribedURLs() + assert.Equal(t, 1, len(list2)) + + mts.UnexportURL(u) + list3, _ := mts.GetExportedURLs(serviceName, group, version, protocol) + assert.Equal(t, 2, len(list3)) + + mts.UnsubscribeURL(u) + list4, _ := mts.GetSubscribedURLs() + assert.Equal(t, 0, len(list4)) + + userProvider := &definition.UserProvider{} + common.ServiceMap.Register(serviceName, protocol, userProvider) + mts.PublishServiceDefinition(u) + expected := "{\"CanonicalName\":\"com.ikurento.user.UserProvider\",\"CodeSource\":\"\"," + + "\"Methods\":[{\"Name\":\"GetUser\",\"ParameterTypes\":[\"slice\"],\"ReturnType\":\"ptr\"," + + "\"Parameters\":null}],\"Types\":null}" + def1, _ := mts.GetServiceDefinition(serviceName, group, version) + assert.Equal(t, expected, def1) + serviceKey := definition.ServiceDescriperBuild(serviceName, group, version) + def2, _ := mts.GetServiceDefinitionByServiceKey(serviceKey) + assert.Equal(t, expected, def2) +} diff --git a/metadata/service/remote/metadata_service_proxy_factory.go b/metadata/service/remote/metadata_service_proxy_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..a1a8594282c581913d97586630f3e5e74305642d --- /dev/null +++ b/metadata/service/remote/metadata_service_proxy_factory.go @@ -0,0 +1,30 @@ +/* + * 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 remote + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/service" +) + +func init() { + factory := service.NewBaseMetadataServiceProxyFactory(newMetadataServiceProxy) + extension.SetMetadataServiceProxyFactory(remote, func() service.MetadataServiceProxyFactory { + return factory + }) +} diff --git a/metadata/service/remote/service.go b/metadata/service/remote/service.go new file mode 100644 index 0000000000000000000000000000000000000000..ae83a69bef0af1614352c99c1e512a63770a0eff --- /dev/null +++ b/metadata/service/remote/service.go @@ -0,0 +1,208 @@ +/* + * 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 remote + +import ( + "sync" +) + +import ( + "go.uber.org/atomic" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/definition" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report/delegate" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/metadata/service/inmemory" +) + +// version will be used by Version func +const ( + version = "1.0.0" + remote = "remote" +) + +func init() { + extension.SetMetadataService(remote, newMetadataService) +} + +// MetadataService is a implement of metadata service which will delegate the remote metadata report +// This is singleton +type MetadataService struct { + service.BaseMetadataService + inMemoryMetadataService *inmemory.MetadataService + exportedRevision atomic.String + subscribedRevision atomic.String + delegateReport *delegate.MetadataReport +} + +var ( + metadataServiceOnce sync.Once + metadataServiceInstance *MetadataService +) + +// newMetadataService will create a new remote MetadataService instance +func newMetadataService() (service.MetadataService, error) { + var err error + metadataServiceOnce.Do(func() { + var mr *delegate.MetadataReport + mr, err = delegate.NewMetadataReport() + if err != nil { + return + } + // it will never return error + inms, _ := inmemory.NewMetadataService() + metadataServiceInstance = &MetadataService{ + BaseMetadataService: service.NewBaseMetadataService(config.GetApplicationConfig().Name), + inMemoryMetadataService: inms.(*inmemory.MetadataService), + delegateReport: mr, + } + }) + return metadataServiceInstance, err +} + +// setInMemoryMetadataService will replace the in memory metadata service by the specific param +func (mts *MetadataService) setInMemoryMetadataService(metadata *inmemory.MetadataService) { + mts.inMemoryMetadataService = metadata +} + +// ExportURL will be implemented by in memory service +func (mts *MetadataService) ExportURL(url common.URL) (bool, error) { + return mts.inMemoryMetadataService.ExportURL(url) +} + +// UnexportURL remove @url's metadata +func (mts *MetadataService) UnexportURL(url common.URL) error { + smi := identifier.NewServiceMetadataIdentifier(url) + smi.Revision = mts.exportedRevision.Load() + return mts.delegateReport.RemoveServiceMetadata(smi) +} + +// SubscribeURL will be implemented by in memory service +func (mts *MetadataService) SubscribeURL(url common.URL) (bool, error) { + return mts.inMemoryMetadataService.SubscribeURL(url) +} + +// UnsubscribeURL will be implemented by in memory service +func (mts *MetadataService) UnsubscribeURL(url common.URL) error { + return mts.UnsubscribeURL(url) +} + +// PublishServiceDefinition will call remote metadata's StoreProviderMetadata to store url info and service definition +func (mts *MetadataService) PublishServiceDefinition(url common.URL) error { + interfaceName := url.GetParam(constant.INTERFACE_KEY, "") + isGeneric := url.GetParamBool(constant.GENERIC_KEY, false) + if len(interfaceName) > 0 && !isGeneric { + sv := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) + sd := definition.BuildServiceDefinition(*sv, url) + id := &identifier.MetadataIdentifier{ + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: interfaceName, + Version: url.GetParam(constant.VERSION_KEY, ""), + // Group: url.GetParam(constant.GROUP_KEY, constant.SERVICE_DISCOVERY_DEFAULT_GROUP), + Group: url.GetParam(constant.GROUP_KEY, constant.DUBBO), + Side: url.GetParam(constant.SIDE_KEY, "provider"), + }, + } + mts.delegateReport.StoreProviderMetadata(id, sd) + return nil + } + logger.Errorf("publishProvider interfaceName is empty . providerUrl:%v ", url) + return nil +} + +// GetExportedURLs will be implemented by in memory service +func (mts *MetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + return mts.inMemoryMetadataService.GetExportedURLs(serviceInterface, group, version, protocol) +} + +// GetSubscribedURLs will be implemented by in memory service +func (mts *MetadataService) GetSubscribedURLs() ([]common.URL, error) { + return mts.inMemoryMetadataService.GetSubscribedURLs() +} + +// GetServiceDefinition will be implemented by in memory service +func (mts *MetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + return mts.inMemoryMetadataService.GetServiceDefinition(interfaceName, group, version) +} + +// GetServiceDefinitionByServiceKey will be implemented by in memory service +func (mts *MetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + return mts.inMemoryMetadataService.GetServiceDefinitionByServiceKey(serviceKey) +} + +// RefreshMetadata will refresh the exported & subscribed metadata to remote metadata report from the inmemory metadata service +func (mts *MetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + if len(exportedRevision) != 0 && exportedRevision != mts.exportedRevision.Load() { + mts.exportedRevision.Store(exportedRevision) + urls, err := mts.inMemoryMetadataService.GetExportedURLs(constant.ANY_VALUE, "", "", "") + if err != nil { + logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %+v", err) + return false, err + } + logger.Infof("urls length = %v", len(urls)) + for _, ui := range urls { + + u, err := common.NewURL(ui.(string)) + if err != nil { + logger.Errorf("this is not valid url string: %s ", ui.(string)) + continue + } + id := identifier.NewServiceMetadataIdentifier(u) + id.Revision = mts.exportedRevision.Load() + if err := mts.delegateReport.SaveServiceMetadata(id, u); err != nil { + logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %+v", err) + return false, err + } + } + } + + if len(subscribedRevision) != 0 && subscribedRevision != mts.subscribedRevision.Load() { + mts.subscribedRevision.Store(subscribedRevision) + urls, err := mts.inMemoryMetadataService.GetSubscribedURLs() + if err != nil { + logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %v+", err) + return false, err + } + if urls != nil && len(urls) > 0 { + id := &identifier.SubscriberMetadataIdentifier{ + MetadataIdentifier: identifier.MetadataIdentifier{ + Application: config.GetApplicationConfig().Name, + }, + Revision: subscribedRevision, + } + if err := mts.delegateReport.SaveSubscribedData(id, urls); err != nil { + logger.Errorf("Error occur when execute remote.MetadataService.RefreshMetadata, error message is %+v", err) + return false, err + } + } + } + return true, nil +} + +// Version will return the remote service version +func (MetadataService) Version() (string, error) { + return version, nil +} diff --git a/metadata/service/remote/service_proxy.go b/metadata/service/remote/service_proxy.go new file mode 100644 index 0000000000000000000000000000000000000000..eaf7a02f4a0f3a8280835940bd8da720a0bde9f5 --- /dev/null +++ b/metadata/service/remote/service_proxy.go @@ -0,0 +1,162 @@ +/* + * 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 remote + +import ( + "strings" +) +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config/instance" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" +) + +type metadataServiceProxy struct { + serviceName string + revision string + report report.MetadataReport +} + +func (m *metadataServiceProxy) Reference() string { + return constant.METADATA_SERVICE_NAME +} + +func (m *metadataServiceProxy) ServiceName() (string, error) { + return m.serviceName, nil +} + +func (m *metadataServiceProxy) ExportURL(url common.URL) (bool, error) { + logger.Error("you should never invoke this implementation") + return true, nil +} + +func (m *metadataServiceProxy) UnexportURL(url common.URL) error { + logger.Error("you should never invoke this implementation") + return nil +} + +func (m *metadataServiceProxy) SubscribeURL(url common.URL) (bool, error) { + logger.Error("you should never invoke this implementation") + return true, nil +} + +func (m *metadataServiceProxy) UnsubscribeURL(url common.URL) error { + logger.Error("you should never invoke this implementation") + return nil +} + +func (m *metadataServiceProxy) PublishServiceDefinition(url common.URL) error { + logger.Error("you should never invoke this implementation") + return nil +} + +func (m *metadataServiceProxy) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + urls, err := m.report.GetExportedURLs(&identifier.ServiceMetadataIdentifier{ + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: serviceInterface, + Version: version, + Group: group, + Side: constant.PROVIDER_PROTOCOL, + }, + Revision: m.revision, + Protocol: protocol, + }) + + if err != nil { + return []interface{}{}, nil + } + res := make([]common.URL, 0, len(urls)) + for _, s := range urls { + u, err := common.NewURL(s) + if err != nil { + logger.Errorf("could not parse the url string to URL structure", err) + continue + } + res = append(res, u) + } + return service.ConvertURLArrToIntfArr(res), nil +} + +func (m *metadataServiceProxy) MethodMapper() map[string]string { + return map[string]string{} +} + +func (m *metadataServiceProxy) GetSubscribedURLs() ([]common.URL, error) { + logger.Error("you should never invoke this implementation") + return []common.URL{}, nil +} + +func (m *metadataServiceProxy) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + return m.report.GetServiceDefinition(&identifier.MetadataIdentifier{ + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: interfaceName, + Group: group, + Version: version, + Side: constant.PROVIDER_PROTOCOL, + }, + Application: m.serviceName, + }) +} + +func (m *metadataServiceProxy) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + params := parse(serviceKey) + return m.GetServiceDefinition(params[0], params[1], params[2]) +} + +func (m *metadataServiceProxy) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + logger.Error("you should never invoke this implementation") + return true, nil +} + +func (m metadataServiceProxy) Version() (string, error) { + return version, nil +} + +func newMetadataServiceProxy(ins registry.ServiceInstance) service.MetadataService { + revision := ins.GetMetadata()[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME] + if len(revision) == 0 { + revision = constant.DEFAULT_REVIESION + } + + return &metadataServiceProxy{ + serviceName: ins.GetServiceName(), + revision: revision, + report: instance.GetMetadataReportInstance(), + } +} + +func parse(key string) []string { + arr := make([]string, 3, 3) + tmp := strings.SplitN(key, "/", 2) + if len(tmp) > 1 { + arr[0] = tmp[0] + key = tmp[1] + } + tmp = strings.SplitN(key, "/", 2) + if len(tmp) > 1 { + arr[2] = tmp[1] + key = tmp[0] + } + arr[1] = key + return arr +} diff --git a/metadata/service/remote/service_proxy_test.go b/metadata/service/remote/service_proxy_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c284bb22123e731f3b8905f70508856bc767ace6 --- /dev/null +++ b/metadata/service/remote/service_proxy_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 remote + +import ( + "testing" +) +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config/instance" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/report/factory" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" +) + +func TestMetadataServiceProxy_GetExportedURLs(t *testing.T) { + pxy := createProxy() + res, err := pxy.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + assert.Nil(t, err) + assert.Len(t, res, 2) +} + +func TestMetadataServiceProxy_GetServiceDefinition(t *testing.T) { + pxy := createProxy() + res, err := pxy.GetServiceDefinition(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + assert.Nil(t, err) + assert.Equal(t, "definition", res) +} + +// TestMetadataServiceProxy test those unimportant method +// in fact, we don't use them +func TestMetadataServiceProxy(t *testing.T) { + pxy := createProxy() + pxy.ServiceName() + pxy.PublishServiceDefinition(common.URL{}) + pxy.Version() + pxy.GetSubscribedURLs() + pxy.UnsubscribeURL(common.URL{}) + pxy.GetServiceDefinitionByServiceKey("any") + pxy.ExportURL(common.URL{}) + pxy.SubscribeURL(common.URL{}) + pxy.MethodMapper() + pxy.UnexportURL(common.URL{}) + pxy.Reference() + pxy.RefreshMetadata(constant.ANY_VALUE, constant.ANY_VALUE) +} + +func createProxy() service.MetadataService { + + prepareTest() + + ins := ®istry.DefaultServiceInstance{ + Id: "test-id", + ServiceName: "com.dubbo", + Host: "localhost", + Port: 8080, + Enable: true, + Healthy: true, + Metadata: map[string]string{constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME: `{"mock":{"timeout":"10000","version":"1.0.0","dubbo":"2.0.2","release":"2.7.6","port":"20880"}}`}, + } + return newMetadataServiceProxy(ins) +} + +func prepareTest() { + extension.SetMetadataReportFactory("mock", func() factory.MetadataReportFactory { + return &mockMetadataReportFactory{} + }) + u, _ := common.NewURL("mock://localhost") + instance.GetMetadataReportInstance(&u) +} + +type mockMetadataReportFactory struct { +} + +func (m *mockMetadataReportFactory) CreateMetadataReport(*common.URL) report.MetadataReport { + return &mockMetadataReport{} +} + +type mockMetadataReport struct { +} + +func (m mockMetadataReport) StoreProviderMetadata(*identifier.MetadataIdentifier, string) error { + panic("implement me") +} + +func (m mockMetadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error { + panic("implement me") +} + +func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error { + return nil +} + +func (m mockMetadataReport) RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error { + panic("implement me") +} + +func (m mockMetadataReport) GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error) { + return []string{"mock://localhost1", "mock://localhost2"}, nil +} + +func (m mockMetadataReport) SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, string) error { + return nil +} + +func (m mockMetadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error) { + panic("implement me") +} + +func (m mockMetadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) (string, error) { + return "definition", nil +} diff --git a/metadata/service/remote/service_test.go b/metadata/service/remote/service_test.go new file mode 100644 index 0000000000000000000000000000000000000000..734f0989ab06ef17caeef241cd067c678fb8b2ad --- /dev/null +++ b/metadata/service/remote/service_test.go @@ -0,0 +1,143 @@ +/* + * 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 remote + +import ( + "fmt" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config/instance" + "github.com/apache/dubbo-go/metadata/definition" + "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/metadata/report" + "github.com/apache/dubbo-go/metadata/report/factory" + "github.com/apache/dubbo-go/metadata/service/inmemory" +) + +var ( + serviceMetadata = make(map[*identifier.ServiceMetadataIdentifier]common.URL, 4) + subscribedMetadata = make(map[*identifier.SubscriberMetadataIdentifier]string, 4) +) + +func getMetadataReportFactory() factory.MetadataReportFactory { + return &metadataReportFactory{} +} + +type metadataReportFactory struct { +} + +func (mrf *metadataReportFactory) CreateMetadataReport(*common.URL) report.MetadataReport { + return &metadataReport{} +} + +type metadataReport struct { +} + +func (metadataReport) StoreProviderMetadata(*identifier.MetadataIdentifier, string) error { + return nil +} + +func (metadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error { + return nil +} + +func (mr *metadataReport) SaveServiceMetadata(id *identifier.ServiceMetadataIdentifier, url common.URL) error { + logger.Infof("SaveServiceMetadata , url is %v", url) + serviceMetadata[id] = url + return nil +} + +func (metadataReport) RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error { + return nil +} + +func (metadataReport) GetExportedURLs(*identifier.ServiceMetadataIdentifier) ([]string, error) { + return nil, nil +} + +func (mr *metadataReport) SaveSubscribedData(id *identifier.SubscriberMetadataIdentifier, urls string) error { + logger.Infof("SaveSubscribedData, , url is %v", urls) + subscribedMetadata[id] = urls + return nil +} + +func (metadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) ([]string, error) { + return nil, nil +} + +func (metadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) (string, error) { + return "", nil +} + +func TestMetadataService(t *testing.T) { + extension.SetMetadataReportFactory("mock", getMetadataReportFactory) + u, err := common.NewURL(fmt.Sprintf("mock://127.0.0.1:20000/?sync.report=true")) + assert.NoError(t, err) + instance.GetMetadataReportInstance(&u) + mts, err := newMetadataService() + assert.NoError(t, err) + mts.(*MetadataService).setInMemoryMetadataService(mockInmemoryProc(t)) + _, _ = mts.RefreshMetadata("0.0.1", "0.0.1") +} + +func mockInmemoryProc(t *testing.T) *inmemory.MetadataService { + mts, _ := inmemory.NewMetadataService() + serviceName := "com.ikurento.user.UserProvider" + group := "group1" + version := "0.0.1" + protocol := "dubbo" + beanName := "UserProvider" + userProvider := &definition.UserProvider{} + + u, err := common.NewURL(fmt.Sprintf( + "%v://127.0.0.1:20000/com.ikurento.user.UserProvider1?anyhost=true&"+ + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ + "environment=dev&interface=%v&ip=192.168.56.1&methods=GetUser&module=dubbogo+user-info+server&org=ikurento.com&"+ + "owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245&group=%v&version=%v&bean.name=%v", + protocol, serviceName, group, version, beanName)) + assert.NoError(t, err) + + _, err = mts.ExportURL(u) + assert.NoError(t, err) + _, err = mts.SubscribeURL(u) + assert.NoError(t, err) + + _, err = common.ServiceMap.Register(serviceName, protocol, userProvider) + assert.NoError(t, err) + err = mts.PublishServiceDefinition(u) + assert.NoError(t, err) + + expected := "{\"CanonicalName\":\"com.ikurento.user.UserProvider\",\"CodeSource\":\"\"," + + "\"Methods\":[{\"Name\":\"GetUser\",\"ParameterTypes\":[\"slice\"],\"ReturnType\":\"ptr\"," + + "\"Parameters\":null}],\"Types\":null}" + def1, _ := mts.GetServiceDefinition(serviceName, group, version) + assert.Equal(t, expected, def1) + serviceKey := definition.ServiceDescriperBuild(serviceName, group, version) + def2, _ := mts.GetServiceDefinitionByServiceKey(serviceKey) + assert.Equal(t, expected, def2) + return mts.(*inmemory.MetadataService) +} diff --git a/metadata/service/service.go b/metadata/service/service.go new file mode 100644 index 0000000000000000000000000000000000000000..f6509d0a72eb26e488dfb4fdeef5f4bbfd6b1bea --- /dev/null +++ b/metadata/service/service.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 service + +import ( + "sync" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/registry" +) + +// MetadataService is used to define meta data related behaviors +// usually the implementation should be singleton +type MetadataService interface { + common.RPCService + // ServiceName will get the service's name in meta service , which is application name + ServiceName() (string, error) + // ExportURL will store the exported url in metadata + ExportURL(url common.URL) (bool, error) + // UnexportURL will delete the exported url in metadata + UnexportURL(url common.URL) error + // SubscribeURL will store the subscribed url in metadata + SubscribeURL(url common.URL) (bool, error) + // UnsubscribeURL will delete the subscribed url in metadata + UnsubscribeURL(url common.URL) error + // PublishServiceDefinition will generate the target url's code info + PublishServiceDefinition(url common.URL) error + // GetExportedURLs will get the target exported url in metadata + // the url should be unique + // due to dubbo-go only support return array []interface{} in RPCService, so we should declare the return type as []interface{} + // actually, it's []String + GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) + + MethodMapper() map[string]string + + // GetExportedURLs will get the target subscribed url in metadata + // the url should be unique + GetSubscribedURLs() ([]common.URL, error) + // GetServiceDefinition will get the target service info store in metadata + GetServiceDefinition(interfaceName string, group string, version string) (string, error) + // GetServiceDefinition will get the target service info store in metadata by service key + GetServiceDefinitionByServiceKey(serviceKey string) (string, error) + // RefreshMetadata will refresh the metadata + RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) + // Version will return the metadata service version + Version() (string, error) +} + +// BaseMetadataService is used for the event logic for struct who will implement interface MetadataService +type BaseMetadataService struct { + serviceName string +} + +func NewBaseMetadataService(serviceName string) BaseMetadataService { + return BaseMetadataService{ + serviceName: serviceName, + } +} + +func (mts *BaseMetadataService) MethodMapper() map[string]string { + return map[string]string{ + "GetExportedURLs": "getExportedURLs", + } +} + +// ServiceName can get the service's name in meta service , which is application name +func (mts *BaseMetadataService) ServiceName() (string, error) { + return mts.serviceName, nil +} + +// Version will return the version of metadata service +func (mts *BaseMetadataService) Reference() string { + return constant.SIMPLE_METADATA_SERVICE_NAME +} + +type MetadataServiceProxyFactory interface { + GetProxy(ins registry.ServiceInstance) MetadataService +} + +type MetadataServiceProxyCreator func(ins registry.ServiceInstance) MetadataService + +type BaseMetadataServiceProxyFactory struct { + proxies sync.Map + creator MetadataServiceProxyCreator +} + +func NewBaseMetadataServiceProxyFactory(creator MetadataServiceProxyCreator) *BaseMetadataServiceProxyFactory { + return &BaseMetadataServiceProxyFactory{ + creator: creator, + } +} + +func (b *BaseMetadataServiceProxyFactory) GetProxy(ins registry.ServiceInstance) MetadataService { + key := ins.GetServiceName() + "##" + getExportedServicesRevision(ins) + if proxy, ok := b.proxies.Load(key); ok { + return proxy.(MetadataService) + } + v, _ := b.proxies.LoadOrStore(key, b.creator(ins)) + return v.(MetadataService) +} + +func getExportedServicesRevision(serviceInstance registry.ServiceInstance) string { + metaData := serviceInstance.GetMetadata() + return metaData[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME] +} + +func ConvertURLArrToIntfArr(urls []common.URL) []interface{} { + if len(urls) == 0 { + return []interface{}{} + } + + res := make([]interface{}, 0, len(urls)) + for _, u := range urls { + res = append(res, u.String()) + } + return res +} diff --git a/protocol/dubbo/dubbo_invoker_test.go b/protocol/dubbo/dubbo_invoker_test.go index 9585461434a899ee3e4a7ca689a77b3a7d1b2b27..3c68b63f7b1678075d505799044d1a9b1a7e1da4 100644 --- a/protocol/dubbo/dubbo_invoker_test.go +++ b/protocol/dubbo/dubbo_invoker_test.go @@ -42,7 +42,7 @@ import ( "github.com/apache/dubbo-go/remoting/getty" ) -func TestDubboInvoker_Invoke(t *testing.T) { +func TestDubboInvokerInvoke(t *testing.T) { proto, url := InitTest(t) c := getExchangeClient(url) diff --git a/protocol/dubbo/dubbo_protocol_test.go b/protocol/dubbo/dubbo_protocol_test.go index 07b890f1345eaf994b6d7ba90642a116526355c4..352d980017932f7c2f8f2538bb0c689ebe4e2ee2 100644 --- a/protocol/dubbo/dubbo_protocol_test.go +++ b/protocol/dubbo/dubbo_protocol_test.go @@ -82,14 +82,6 @@ func TestDubboProtocol_Export(t *testing.T) { "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") - assert.NoError(t, err) - exporter := proto.Export(protocol.NewBaseInvoker(url)) - - // make sure url - eq := exporter.GetInvoker().GetUrl().URLEqual(url) - assert.True(t, eq) - - // second service: the same path and the different version url2, err := common.NewURL("dubbo://127.0.0.1:20095/com.ikurento.user.UserProvider?anyhost=true&"+ "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+ "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+ @@ -104,7 +96,7 @@ func TestDubboProtocol_Export(t *testing.T) { // make sure exporterMap after 'Unexport' _, ok := proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) assert.True(t, ok) - exporter.Unexport() + exporter2.Unexport() _, ok = proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) assert.False(t, ok) diff --git a/protocol/grpc/common_test.go b/protocol/grpc/common_test.go index 33c2fc617d52795d13d9b4fc02054ef5a79d0934..b732283a9bf9d316c1b9d4356a1b9563bfa1d3ec 100644 --- a/protocol/grpc/common_test.go +++ b/protocol/grpc/common_test.go @@ -106,7 +106,7 @@ func dubboGreeterSayHelloHandler(srv interface{}, ctx context.Context, Server: srv, FullMethod: "/helloworld.Greeter/SayHello", } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { + handler := func(context.Context, interface{}) (interface{}, error) { result := base.GetProxyImpl().Invoke(context.Background(), invo) return result.Result(), result.Error() } diff --git a/protocol/grpc/grpc_exporter.go b/protocol/grpc/grpc_exporter.go index 79962b59e29bb0e3aeb58776f6c26abc2e6832de..0dc764854d61576892800180041c53f0a7735c7c 100644 --- a/protocol/grpc/grpc_exporter.go +++ b/protocol/grpc/grpc_exporter.go @@ -28,7 +28,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// GrpcExporter ... +// nolint type GrpcExporter struct { *protocol.BaseExporter } diff --git a/protocol/grpc/grpc_invoker.go b/protocol/grpc/grpc_invoker.go index e150d05e6fb39890bd3e355f4042b4ef34db42ed..737e8c40a063b07e56d7c90f7de04670461ce103 100644 --- a/protocol/grpc/grpc_invoker.go +++ b/protocol/grpc/grpc_invoker.go @@ -37,14 +37,14 @@ import ( var errNoReply = errors.New("request need @response") -// GrpcInvoker ... +// nolint type GrpcInvoker struct { protocol.BaseInvoker quitOnce sync.Once client *Client } -// NewGrpcInvoker ... +// NewGrpcInvoker returns a Grpc invoker instance func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker { return &GrpcInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), @@ -52,7 +52,7 @@ func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker { } } -// Invoke ... +// Invoke is used to call service method by invocation func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( result protocol.RPCResult @@ -81,17 +81,17 @@ func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio return &result } -// IsAvailable ... +// IsAvailable get available status func (gi *GrpcInvoker) IsAvailable() bool { return gi.BaseInvoker.IsAvailable() && gi.client.GetState() != connectivity.Shutdown } -// IsDestroyed ... +// IsDestroyed get destroyed status func (gi *GrpcInvoker) IsDestroyed() bool { return gi.BaseInvoker.IsDestroyed() && gi.client.GetState() == connectivity.Shutdown } -// Destroy ... +// Destroy will destroy gRPC's invoker and client, so it is only called once func (gi *GrpcInvoker) Destroy() { gi.quitOnce.Do(func() { gi.BaseInvoker.Destroy() diff --git a/protocol/grpc/grpc_invoker_test.go b/protocol/grpc/grpc_invoker_test.go index 3054ada13340a9c9cc038a63d89c45ced9ec7ac7..d5ebbb4f47a324791a3367a649bd49b06281540f 100644 --- a/protocol/grpc/grpc_invoker_test.go +++ b/protocol/grpc/grpc_invoker_test.go @@ -33,11 +33,19 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) +const ( + mockGrpcCommonUrl = "grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl" + + "&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter" + + "&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=" + + "&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=" + + "&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!" +) + func TestInvoke(t *testing.T) { go internal.InitGrpcServer() defer internal.ShutdownGrpcServer() - url, err := common.NewURL("grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") + url, err := common.NewURL(mockGrpcCommonUrl) assert.Nil(t, err) cli := NewClient(url) diff --git a/protocol/grpc/grpc_protocol_test.go b/protocol/grpc/grpc_protocol_test.go index d028f8ef4285b0183e6e0b5b32deede59ce5c531..87ce714fc7437eca53509bb368ed7bc774929634 100644 --- a/protocol/grpc/grpc_protocol_test.go +++ b/protocol/grpc/grpc_protocol_test.go @@ -32,12 +32,12 @@ import ( "github.com/apache/dubbo-go/protocol/grpc/internal" ) -func TestGrpcProtocol_Export(t *testing.T) { +func TestGrpcProtocolExport(t *testing.T) { // Export addService() proto := GetProtocol() - url, err := common.NewURL("grpc://127.0.0.1:40000/GrpcGreeterImpl?accesslog=&app.version=0.0.1&application=BDTService&bean.name=GrpcGreeterImpl&cluster=failover&environment=dev&execute.limit=&execute.limit.rejected.handler=&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown×tamp=1576923717&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100") + url, err := common.NewURL(mockGrpcCommonUrl) assert.NoError(t, err) exporter := proto.Export(protocol.NewBaseInvoker(url)) time.Sleep(time.Second) @@ -61,13 +61,13 @@ func TestGrpcProtocol_Export(t *testing.T) { assert.False(t, ok) } -func TestGrpcProtocol_Refer(t *testing.T) { +func TestGrpcProtocolRefer(t *testing.T) { go internal.InitGrpcServer() defer internal.ShutdownGrpcServer() time.Sleep(time.Second) proto := GetProtocol() - url, err := common.NewURL("grpc://127.0.0.1:30000/GrpcGreeterImpl?accesslog=&anyhost=true&app.version=0.0.1&application=BDTService&async=false&bean.name=GrpcGreeterImpl&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&execute.limit=&execute.limit.rejected.handler=&generic=false&group=&interface=io.grpc.examples.helloworld.GreeterGrpc%24IGreeter&ip=192.168.1.106&loadbalance=random&methods.SayHello.loadbalance=random&methods.SayHello.retries=1&methods.SayHello.tps.limit.interval=&methods.SayHello.tps.limit.rate=&methods.SayHello.tps.limit.strategy=&methods.SayHello.weight=0&module=dubbogo+say-hello+client&name=BDTService&organization=ikurento.com&owner=ZX&pid=49427&reference.filter=cshutdown®istry.role=3&remote.timestamp=1576923717&retries=&service.filter=echo%2Ctoken%2Caccesslog%2Ctps%2Cexecute%2Cpshutdown&side=provider×tamp=1576923740&tps.limit.interval=&tps.limit.rate=&tps.limit.rejected.handler=&tps.limit.strategy=&tps.limiter=&version=&warmup=100!") + url, err := common.NewURL(mockGrpcCommonUrl) assert.NoError(t, err) invoker := proto.Refer(url) diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index a84b4a7975a57065f565885b33b5e7660981dfb4..5086364097790faf45a1127f6e14ecff7ffbdc86 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -159,6 +159,8 @@ func (r *RPCInvocation) Invoker() protocol.Invoker { // nolint func (r *RPCInvocation) SetInvoker(invoker protocol.Invoker) { + r.lock.Lock() + defer r.lock.Unlock() r.invoker = invoker } diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go index 5fca66d99399b2974f858cbedb31d9615a303637..2a2ddfeeeb52b865e96ccff69d2b39d8a671ed41 100644 --- a/protocol/jsonrpc/http.go +++ b/protocol/jsonrpc/http.go @@ -47,7 +47,7 @@ import ( // Request // //////////////////////////////////////////// -// Request ... +// Request is HTTP protocol request type Request struct { ID int64 group string diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go index 188b036dfca5087f4729afbc64017d27b9aee902..ae97cf5292b0bbf52372b52da4bb70bc14f084ed 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -44,7 +44,15 @@ type ( } ) -func TestHTTPClient_Call(t *testing.T) { +const ( + mockJsonCommonUrl = "jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider" +) + +func TestHTTPClientCall(t *testing.T) { methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "jsonrpc", &UserProvider{}) assert.NoError(t, err) @@ -52,11 +60,7 @@ func TestHTTPClient_Call(t *testing.T) { // Export proto := GetProtocol() - url, err := common.NewURL("jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + url, err := common.NewURL(mockJsonCommonUrl) assert.NoError(t, err) proto.Export(&proxy_factory.ProxyInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), diff --git a/protocol/jsonrpc/json.go b/protocol/jsonrpc/json.go index 389ead9c1a530742c872a238d89b2df5ae3462ca..506c4c953b1b1113b43669171efdeeaeb6fca10d 100644 --- a/protocol/jsonrpc/json.go +++ b/protocol/jsonrpc/json.go @@ -124,10 +124,8 @@ func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) { if param != nil { switch k := reflect.TypeOf(param).Kind(); k { case reflect.Map: - if reflect.TypeOf(param).Key().Kind() == reflect.String { - if reflect.ValueOf(param).IsNil() { - param = nil - } + if reflect.TypeOf(param).Key().Kind() == reflect.String && reflect.ValueOf(param).IsNil() { + param = nil } case reflect.Slice: if reflect.ValueOf(param).IsNil() { @@ -137,10 +135,8 @@ func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) { case reflect.Ptr: switch ptrK := reflect.TypeOf(param).Elem().Kind(); ptrK { case reflect.Map: - if reflect.TypeOf(param).Elem().Key().Kind() == reflect.String { - if reflect.ValueOf(param).Elem().IsNil() { - param = nil - } + if reflect.TypeOf(param).Elem().Key().Kind() == reflect.String && reflect.ValueOf(param).Elem().IsNil() { + param = nil } case reflect.Slice: if reflect.ValueOf(param).Elem().IsNil() { @@ -292,7 +288,7 @@ type ServerCodec struct { var ( null = json.RawMessage([]byte("null")) - // Version ... + // Version is json RPC's version Version = "2.0" ) diff --git a/protocol/jsonrpc/json_test.go b/protocol/jsonrpc/json_test.go index ade74246121b5f275c8dbeaa5923228dbab2804f..a3814e9ad5e1df2c3a8d8cc4305e5787b44596ec 100644 --- a/protocol/jsonrpc/json_test.go +++ b/protocol/jsonrpc/json_test.go @@ -30,7 +30,7 @@ type TestData struct { Test string } -func TestJsonClientCodec_Write(t *testing.T) { +func TestJsonClientCodecWrite(t *testing.T) { cd := &CodecData{ ID: 1, Method: "GetUser", @@ -46,7 +46,7 @@ func TestJsonClientCodec_Write(t *testing.T) { assert.EqualError(t, err, "unsupported param type: int") } -func TestJsonClientCodec_Read(t *testing.T) { +func TestJsonClientCodecRead(t *testing.T) { codec := newJsonClientCodec() codec.pending[1] = "GetUser" rsp := &TestData{} @@ -60,7 +60,7 @@ func TestJsonClientCodec_Read(t *testing.T) { assert.EqualError(t, err, "{\"code\":-32000,\"message\":\"error\"}") } -func TestServerCodec_Write(t *testing.T) { +func TestServerCodecWrite(t *testing.T) { codec := newServerCodec() a := json.RawMessage([]byte("1")) codec.req = serverRequest{Version: "1.0", Method: "GetUser", ID: &a} @@ -73,7 +73,7 @@ func TestServerCodec_Write(t *testing.T) { assert.Equal(t, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"Test\":\"test\"},\"error\":{\"code\":-32000,\"message\":\"error\"}}\n", string(data)) } -func TestServerCodec_Read(t *testing.T) { +func TestServerCodecRead(t *testing.T) { codec := newServerCodec() header := map[string]string{} err := codec.ReadHeader(header, []byte("{\"jsonrpc\":\"2.0\",\"method\":\"GetUser\",\"params\":[\"args\",2],\"id\":1}\n")) diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go index 0f14ba11e2dec18bbd4d63e87e8c0fb2727f3755..d7124ca07c6ba6d79dc72e7fb6bd98cd4b3a97b2 100644 --- a/protocol/jsonrpc/jsonrpc_invoker_test.go +++ b/protocol/jsonrpc/jsonrpc_invoker_test.go @@ -34,7 +34,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func TestJsonrpcInvoker_Invoke(t *testing.T) { +func TestJsonrpcInvokerInvoke(t *testing.T) { methods, err := common.ServiceMap.Register("UserProvider", "jsonrpc", &UserProvider{}) assert.NoError(t, err) diff --git a/protocol/jsonrpc/jsonrpc_protocol_test.go b/protocol/jsonrpc/jsonrpc_protocol_test.go index c00bed12fe9fbb4937f21810cee548a25e3b1c05..10a9016913c77551efc54a92149ef377ade399f5 100644 --- a/protocol/jsonrpc/jsonrpc_protocol_test.go +++ b/protocol/jsonrpc/jsonrpc_protocol_test.go @@ -34,7 +34,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -func TestJsonrpcProtocol_Export(t *testing.T) { +func TestJsonrpcProtocolExport(t *testing.T) { // Export proto := GetProtocol() url, err := common.NewURL("jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + @@ -65,7 +65,7 @@ func TestJsonrpcProtocol_Export(t *testing.T) { assert.False(t, ok) } -func TestJsonrpcProtocol_Refer(t *testing.T) { +func TestJsonrpcProtocolRefer(t *testing.T) { // Refer proto := GetProtocol() url, err := common.NewURL("jsonrpc://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + diff --git a/protocol/protocolwrapper/mock_protocol_filter.go b/protocol/protocolwrapper/mock_protocol_filter.go index 8763c20410915d4e81afd35691be7d9ed490f7a1..2e9ffed06f03d196c91f6679df21538612bac405 100644 --- a/protocol/protocolwrapper/mock_protocol_filter.go +++ b/protocol/protocolwrapper/mock_protocol_filter.go @@ -45,5 +45,5 @@ func (pfw *mockProtocolFilter) Refer(url common.URL) protocol.Invoker { // Destroy will do nothing func (pfw *mockProtocolFilter) Destroy() { - + return } diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index 6cb343c160323805836aa00a78def97bf5878b54..2d8046087fb5055f3e1fb2f1ff5a04c56660aeb1 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -89,7 +89,7 @@ func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { return next } -// GetProtocol ... +// nolint func GetProtocol() protocol.Protocol { return &ProtocolFilterWrapper{} } @@ -98,30 +98,30 @@ func GetProtocol() protocol.Protocol { // filter invoker /////////////////////////// -// FilterInvoker ... +// FilterInvoker defines invoker and filter type FilterInvoker struct { next protocol.Invoker invoker protocol.Invoker filter filter.Filter } -// GetUrl ... +// GetUrl is used to get url from FilterInvoker func (fi *FilterInvoker) GetUrl() common.URL { return fi.invoker.GetUrl() } -// IsAvailable ... +// IsAvailable is used to get available status func (fi *FilterInvoker) IsAvailable() bool { return fi.invoker.IsAvailable() } -// Invoke ... +// Invoke is used to call service method by invocation func (fi *FilterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { result := fi.filter.Invoke(ctx, fi.next, invocation) return fi.filter.OnResponse(ctx, result, fi.invoker, invocation) } -// Destroy ... +// Destroy will destroy invoker func (fi *FilterInvoker) Destroy() { fi.invoker.Destroy() } diff --git a/protocol/protocolwrapper/protocol_filter_wrapper_test.go b/protocol/protocolwrapper/protocol_filter_wrapper_test.go index 8491d57462d47d6af72040d41b78dcb30e6da697..b03ea7b9b66d926aff8851f88e1bd7434b254903 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper_test.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper_test.go @@ -36,7 +36,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -func TestProtocolFilterWrapper_Export(t *testing.T) { +func TestProtocolFilterWrapperExport(t *testing.T) { filtProto := extension.GetProtocol(FILTER) filtProto.(*ProtocolFilterWrapper).protocol = &protocol.BaseProtocol{} @@ -48,7 +48,7 @@ func TestProtocolFilterWrapper_Export(t *testing.T) { assert.True(t, ok) } -func TestProtocolFilterWrapper_Refer(t *testing.T) { +func TestProtocolFilterWrapperRefer(t *testing.T) { filtProto := extension.GetProtocol(FILTER) filtProto.(*ProtocolFilterWrapper).protocol = &protocol.BaseProtocol{} diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go index b60f50a5a70cde01a051dbb2a4490cbb792e0116..f301d945045a445b5370252044442080f042d126 100644 --- a/protocol/rest/client/client_impl/resty_client.go +++ b/protocol/rest/client/client_impl/resty_client.go @@ -50,7 +50,7 @@ func NewRestyClient(restOption *client.RestOptions) client.RestClient { client := resty.New() client.SetTransport( &http.Transport{ - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { c, err := net.DialTimeout(network, addr, restOption.ConnectTimeout) if err != nil { return nil, err diff --git a/protocol/rest/config/reader/rest_config_reader_test.go b/protocol/rest/config/reader/rest_config_reader_test.go index d2dba40b9b85a6cd7772e0fee619720c79e91eb4..c6f891262ce3ba5ec6645ade9084005d14716789 100644 --- a/protocol/rest/config/reader/rest_config_reader_test.go +++ b/protocol/rest/config/reader/rest_config_reader_test.go @@ -31,7 +31,7 @@ import ( "github.com/apache/dubbo-go/protocol/rest/config" ) -func TestRestConfigReader_ReadConsumerConfig(t *testing.T) { +func TestRestConfigReaderReadConsumerConfig(t *testing.T) { bs, err := yaml.LoadYMLConfig("./testdata/consumer_config.yml") assert.NoError(t, err) configReader := NewRestConfigReader() @@ -40,7 +40,7 @@ func TestRestConfigReader_ReadConsumerConfig(t *testing.T) { assert.NotEmpty(t, config.GetRestConsumerServiceConfigMap()) } -func TestRestConfigReader_ReadProviderConfig(t *testing.T) { +func TestRestConfigReaderReadProviderConfig(t *testing.T) { bs, err := yaml.LoadYMLConfig("./testdata/provider_config.yml") assert.NoError(t, err) configReader := NewRestConfigReader() diff --git a/protocol/rest/config/rest_config.go b/protocol/rest/config/rest_config.go index 168ec8ce525fc7fd5d4a30d4f11ba7bf42d1c921..4732dd8e4eae3ba874fdd8ed95380e6ace3ab66d 100644 --- a/protocol/rest/config/rest_config.go +++ b/protocol/rest/config/rest_config.go @@ -26,7 +26,7 @@ var ( restProviderServiceConfigMap map[string]*RestServiceConfig ) -// RestConsumerConfig ... +// nolint type RestConsumerConfig struct { Client string `default:"resty" yaml:"rest_client" json:"rest_client,omitempty" property:"rest_client"` Produces string `default:"application/json" yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` @@ -34,7 +34,7 @@ type RestConsumerConfig struct { RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"references" json:"references,omitempty" property:"references"` } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestConsumerConfig by @unmarshal function func (c *RestConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -46,7 +46,7 @@ func (c *RestConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) er return nil } -// RestProviderConfig ... +// nolint type RestProviderConfig struct { Server string `default:"go-restful" yaml:"rest_server" json:"rest_server,omitempty" property:"rest_server"` Produces string `default:"*/*" yaml:"rest_produces" json:"rest_produces,omitempty" property:"rest_produces"` @@ -54,7 +54,7 @@ type RestProviderConfig struct { RestServiceConfigsMap map[string]*RestServiceConfig `yaml:"services" json:"services,omitempty" property:"services"` } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestProviderConfig by @unmarshal function func (c *RestProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -66,7 +66,7 @@ func (c *RestProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) er return nil } -// RestServiceConfig ... +// nolint type RestServiceConfig struct { InterfaceName string `required:"true" yaml:"interface" json:"interface,omitempty" property:"interface"` Url string `yaml:"url" json:"url,omitempty" property:"url"` @@ -80,7 +80,7 @@ type RestServiceConfig struct { RestMethodConfigsMap map[string]*RestMethodConfig } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestServiceConfig by @unmarshal function func (c *RestServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -92,7 +92,7 @@ func (c *RestServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) err return nil } -// RestMethodConfig ... +// nolint type RestMethodConfig struct { InterfaceName string MethodName string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` @@ -110,7 +110,7 @@ type RestMethodConfig struct { HeadersMap map[int]string } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RestMethodConfig by @unmarshal function func (c *RestMethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -122,32 +122,32 @@ func (c *RestMethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) erro return nil } -// GetRestConsumerServiceConfig ... +// nolint func GetRestConsumerServiceConfig(path string) *RestServiceConfig { return restConsumerServiceConfigMap[path] } -// GetRestProviderServiceConfig ... +// nolint func GetRestProviderServiceConfig(path string) *RestServiceConfig { return restProviderServiceConfigMap[path] } -// SetRestConsumerServiceConfigMap ... +// nolint func SetRestConsumerServiceConfigMap(configMap map[string]*RestServiceConfig) { restConsumerServiceConfigMap = configMap } -// SetRestProviderServiceConfigMap ... +// nolint func SetRestProviderServiceConfigMap(configMap map[string]*RestServiceConfig) { restProviderServiceConfigMap = configMap } -// GetRestConsumerServiceConfigMap ... +// nolint func GetRestConsumerServiceConfigMap() map[string]*RestServiceConfig { return restConsumerServiceConfigMap } -// GetRestProviderServiceConfigMap ... +// nolint func GetRestProviderServiceConfigMap() map[string]*RestServiceConfig { return restProviderServiceConfigMap } diff --git a/protocol/rest/rest_exporter.go b/protocol/rest/rest_exporter.go index 1ee208615ea07e3f2850920492ab9e9821e7ffef..e39558caeae9811817cc26a1717c1b8e3729234c 100644 --- a/protocol/rest/rest_exporter.go +++ b/protocol/rest/rest_exporter.go @@ -28,16 +28,19 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// nolint type RestExporter struct { protocol.BaseExporter } +// NewRestExporter returns a RestExporter func NewRestExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *RestExporter { return &RestExporter{ BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap), } } +// Unexport unexport the RestExporter func (re *RestExporter) Unexport() { serviceId := re.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") interfaceName := re.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "") diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go index 121d1217efd3baea0f961b67e243e9a0450aefc2..691beeda4085f316075fe55b9328bc86d6328187 100644 --- a/protocol/rest/rest_invoker.go +++ b/protocol/rest/rest_invoker.go @@ -35,12 +35,14 @@ import ( "github.com/apache/dubbo-go/protocol/rest/config" ) +// nolint type RestInvoker struct { protocol.BaseInvoker client client.RestClient restMethodConfigMap map[string]*config.RestMethodConfig } +// NewRestInvoker returns a RestInvoker func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig map[string]*config.RestMethodConfig) *RestInvoker { return &RestInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), @@ -49,6 +51,7 @@ func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig } } +// Invoke is used to call service method by invocation func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { inv := invocation.(*invocation_impl.RPCInvocation) methodConfig := ri.restMethodConfigMap[inv.MethodName()] @@ -95,6 +98,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio return &result } +// restStringMapTransform is used to transform rest map func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[string]string, error) { resMap := make(map[string]string, len(paramsMap)) for k, v := range paramsMap { @@ -106,6 +110,7 @@ func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[s return resMap, nil } +// nolint func getRestHttpHeader(methodConfig *config.RestMethodConfig, args []interface{}) (http.Header, error) { header := http.Header{} headersMap := methodConfig.HeadersMap diff --git a/protocol/rest/rest_invoker_test.go b/protocol/rest/rest_invoker_test.go index 2ea260c58d03c27a691e48b953ce6d64f75040a2..9df97a211e9d90daa3206b94926ceeac42df4606 100644 --- a/protocol/rest/rest_invoker_test.go +++ b/protocol/rest/rest_invoker_test.go @@ -39,7 +39,15 @@ import ( "github.com/apache/dubbo-go/protocol/rest/server/server_impl" ) -func TestRestInvoker_Invoke(t *testing.T) { +const ( + mockRestCommonUrl = "rest://127.0.0.1:8877/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245" +) + +func TestRestInvokerInvoke(t *testing.T) { // Refer proto := GetRestProtocol() defer proto.Destroy() @@ -55,11 +63,7 @@ func TestRestInvoker_Invoke(t *testing.T) { chain.ProcessFilter(request, response) }) - url, err := common.NewURL("rest://127.0.0.1:8877/com.ikurento.user.UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245") + url, err := common.NewURL(mockRestCommonUrl) assert.NoError(t, err) _, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{}) assert.NoError(t, err) diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go index e15eeb39d72212eb9f1d0235313eba231d3b0a36..0cd26c240a7eb1b83a04ca2f57c332bbda994967 100644 --- a/protocol/rest/rest_protocol.go +++ b/protocol/rest/rest_protocol.go @@ -44,10 +44,12 @@ var ( const REST = "rest" +// nolint func init() { extension.SetProtocol(REST, GetRestProtocol) } +// nolint type RestProtocol struct { protocol.BaseProtocol serverLock sync.Mutex @@ -56,6 +58,7 @@ type RestProtocol struct { clientMap map[client.RestOptions]client.RestClient } +// NewRestProtocol returns a RestProtocol func NewRestProtocol() *RestProtocol { return &RestProtocol{ BaseProtocol: protocol.NewBaseProtocol(), @@ -64,6 +67,7 @@ func NewRestProtocol() *RestProtocol { } } +// Export export rest service func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := url.ServiceKey() @@ -81,6 +85,7 @@ func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { return exporter } +// Refer create rest service reference func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { // create rest_invoker var requestTimeout = config.GetConsumerConfig().RequestTimeout @@ -101,6 +106,7 @@ func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { return invoker } +// nolint func (rp *RestProtocol) getServer(url common.URL, serverType string) server.RestServer { restServer, ok := rp.serverMap[url.Location] if ok { @@ -122,6 +128,7 @@ func (rp *RestProtocol) getServer(url common.URL, serverType string) server.Rest return restServer } +// nolint func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType string) client.RestClient { restClient, ok := rp.clientMap[restOptions] if ok { @@ -138,6 +145,7 @@ func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType str return restClient } +// Destroy destroy rest service func (rp *RestProtocol) Destroy() { // destroy rest_server rp.BaseProtocol.Destroy() @@ -150,6 +158,7 @@ func (rp *RestProtocol) Destroy() { } } +// GetRestProtocol get a rest protocol func GetRestProtocol() protocol.Protocol { if restProtocol == nil { restProtocol = NewRestProtocol() diff --git a/protocol/rest/rest_protocol_test.go b/protocol/rest/rest_protocol_test.go index 9117148777ca868cb7d2672236e800c836d3de84..9ff4e7df7fe41d7fd028bf476cc2192cf7cefcca 100644 --- a/protocol/rest/rest_protocol_test.go +++ b/protocol/rest/rest_protocol_test.go @@ -38,14 +38,10 @@ import ( rest_config "github.com/apache/dubbo-go/protocol/rest/config" ) -func TestRestProtocol_Refer(t *testing.T) { +func TestRestProtocolRefer(t *testing.T) { // Refer proto := GetRestProtocol() - url, err := common.NewURL("rest://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245") + url, err := common.NewURL(mockRestCommonUrl) assert.NoError(t, err) con := config.ConsumerConfig{ ConnectTimeout: 5 * time.Second, @@ -71,14 +67,10 @@ func TestRestProtocol_Refer(t *testing.T) { assert.Equal(t, 0, invokersLen) } -func TestRestProtocol_Export(t *testing.T) { +func TestRestProtocolExport(t *testing.T) { // Export proto := GetRestProtocol() - url, err := common.NewURL("rest://127.0.0.1:8888/com.ikurento.user.UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245") + url, err := common.NewURL(mockRestCommonUrl) assert.NoError(t, err) _, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{}) assert.NoError(t, err) diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go index 0eeca6c1bba1f471117eb687a92d0458b9d5901d..60becfb34135470b0e69972c25a743f44efe19d5 100644 --- a/protocol/rpc_status.go +++ b/protocol/rpc_status.go @@ -170,12 +170,12 @@ func CurrentTimeMillis() int64 { // Destroy is used to clean all status func CleanAllStatus() { - delete1 := func(key interface{}, value interface{}) bool { + delete1 := func(key, _ interface{}) bool { methodStatistics.Delete(key) return true } methodStatistics.Range(delete1) - delete2 := func(key interface{}, value interface{}) bool { + delete2 := func(key, _ interface{}) bool { serviceStatistic.Delete(key) return true } diff --git a/protocol/rpc_status_test.go b/protocol/rpc_status_test.go index 611b7cba6e453cdf00624b4414d468278fd62cb1..cc12753cf25007ab6d1010836b203aee22d78ca9 100644 --- a/protocol/rpc_status_test.go +++ b/protocol/rpc_status_test.go @@ -30,10 +30,14 @@ import ( "github.com/apache/dubbo-go/common" ) +const ( + mockCommonDubboUrl = "dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider" +) + func TestBeginCount(t *testing.T) { defer CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(mockCommonDubboUrl) BeginCount(url, "test") urlStatus := GetURLStatus(url) methodStatus := GetMethodStatus(url, "test") @@ -47,7 +51,7 @@ func TestBeginCount(t *testing.T) { func TestEndCount(t *testing.T) { defer CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(mockCommonDubboUrl) EndCount(url, "test", 100, true) urlStatus := GetURLStatus(url) methodStatus := GetMethodStatus(url, "test") @@ -60,7 +64,7 @@ func TestEndCount(t *testing.T) { func TestGetMethodStatus(t *testing.T) { defer CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(mockCommonDubboUrl) status := GetMethodStatus(url, "test") assert.NotNil(t, status) assert.Equal(t, int32(0), status.total) @@ -69,25 +73,25 @@ func TestGetMethodStatus(t *testing.T) { func TestGetUrlStatus(t *testing.T) { defer CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(mockCommonDubboUrl) status := GetURLStatus(url) assert.NotNil(t, status) assert.Equal(t, int32(0), status.total) } -func Test_beginCount0(t *testing.T) { +func TestbeginCount0(t *testing.T) { defer CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(mockCommonDubboUrl) status := GetURLStatus(url) beginCount0(status) assert.Equal(t, int32(1), status.active) } -func Test_All(t *testing.T) { +func TestAll(t *testing.T) { defer CleanAllStatus() - url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") + url, _ := common.NewURL(mockCommonDubboUrl) request(url, "test", 100, false, true) urlStatus := GetURLStatus(url) methodStatus := GetMethodStatus(url, "test") diff --git a/registry/base_configuration_listener.go b/registry/base_configuration_listener.go index 7b28d7ee1b30a03b8211c4c1efa5824c3b61453a..3b36510306680486ba9d269472450df8867b61b1 100644 --- a/registry/base_configuration_listener.go +++ b/registry/base_configuration_listener.go @@ -100,7 +100,12 @@ func ToConfigurators(urls []*common.URL, f func(url *common.URL) config_center.C configurators = []config_center.Configurator{} break } - //TODO:anyhost_key judage + + override := url.GetParams() + delete(override, constant.ANYHOST_KEY) + if len(override) == 0 { + continue + } configurators = append(configurators, f(url)) } return configurators diff --git a/registry/base_registry.go b/registry/base_registry.go index 3e1bddf233310871182544b6415c10c8df27e622..ad1a3b61741e003625612ad58409eb8615271a84 100644 --- a/registry/base_registry.go +++ b/registry/base_registry.go @@ -56,6 +56,8 @@ func init() { localIP, _ = gxnet.GetLocalIP() } +type createPathFunc func(dubboPath string) error + /* * -----------------------------------NOTICE--------------------------------------------- * If there is no special case, you'd better inherit BaseRegistry and implement the @@ -74,8 +76,12 @@ type FacadeBasedRegistry interface { CreatePath(string) error // DoRegister actually do the register job DoRegister(string, string) error + // DoUnregister do the unregister job + DoUnregister(string, string) error // DoSubscribe actually subscribe the URL DoSubscribe(conf *common.URL) (Listener, error) + // DoUnsubscribe does unsubscribe the URL + DoUnsubscribe(conf *common.URL) (Listener, error) // CloseAndNilClient close the client and then reset the client in registry to nil // you should notice that this method will be invoked inside a lock. // So you should implement this method as light weighted as you can. @@ -94,7 +100,7 @@ type BaseRegistry struct { birth int64 // time of file birth, seconds since Epoch; 0 if unknown wg sync.WaitGroup // wg+done for zk restart done chan struct{} - cltLock sync.Mutex //ctl lock is a lock for services map + cltLock sync.RWMutex //ctl lock is a lock for services map services map[string]common.URL // service name + protocol -> service config, for store the service registered } @@ -154,6 +160,43 @@ func (r *BaseRegistry) Register(conf common.URL) error { return nil } +// UnRegister implement interface registry to unregister +func (r *BaseRegistry) UnRegister(conf common.URL) error { + var ( + ok bool + err error + oldURL common.URL + ) + + func() { + r.cltLock.Lock() + defer r.cltLock.Unlock() + oldURL, ok = r.services[conf.Key()] + + if !ok { + err = perrors.Errorf("Path{%s} has not registered", conf.Key()) + } + + delete(r.services, conf.Key()) + }() + + if err != nil { + return err + } + + err = r.unregister(conf) + if err != nil { + func() { + r.cltLock.Lock() + defer r.cltLock.Unlock() + r.services[conf.Key()] = oldURL + }() + return perrors.WithMessagef(err, "register(conf:%+v)", conf) + } + + return nil +} + // service is for getting service path stored in url func (r *BaseRegistry) service(c common.URL) string { return url.QueryEscape(c.Service()) @@ -189,6 +232,18 @@ func (r *BaseRegistry) RestartCallBack() bool { // register for register url to registry, include init params func (r *BaseRegistry) register(c common.URL) error { + return r.processURL(c, r.facadeBasedRegistry.DoRegister, r.createPath) +} + +// unregister for unregister url to registry, include init params +func (r *BaseRegistry) unregister(c common.URL) error { + return r.processURL(c, r.facadeBasedRegistry.DoUnregister, nil) +} + +func (r *BaseRegistry) processURL(c common.URL, f func(string, string) error, cpf createPathFunc) error { + if f == nil { + panic(" Must provide a `function(string, string) error` to process URL. ") + } var ( err error //revision string @@ -213,15 +268,15 @@ func (r *BaseRegistry) register(c common.URL) error { switch role { case common.PROVIDER: - dubboPath, rawURL, err = r.providerRegistry(c, params) + dubboPath, rawURL, err = r.providerRegistry(c, params, cpf) case common.CONSUMER: - dubboPath, rawURL, err = r.consumerRegistry(c, params) + dubboPath, rawURL, err = r.consumerRegistry(c, params, cpf) default: return perrors.Errorf("@c{%v} type is not referencer or provider", c) } encodedURL = url.QueryEscape(rawURL) dubboPath = strings.ReplaceAll(dubboPath, "$", "%24") - err = r.facadeBasedRegistry.DoRegister(dubboPath, encodedURL) + err = f(dubboPath, encodedURL) if err != nil { return perrors.WithMessagef(err, "register Node(path:%s, url:%s)", dubboPath, rawURL) @@ -229,8 +284,15 @@ func (r *BaseRegistry) register(c common.URL) error { return nil } +// createPath will create dubbo path in register +func (r *BaseRegistry) createPath(dubboPath string) error { + r.cltLock.Lock() + defer r.cltLock.Unlock() + return r.facadeBasedRegistry.CreatePath(dubboPath) +} + // providerRegistry for provider role do -func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string, string, error) { +func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values, f createPathFunc) (string, string, error) { var ( dubboPath string rawURL string @@ -240,11 +302,9 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string return "", "", perrors.Errorf("conf{Path:%s, Methods:%s}", c.Path, c.Methods) } dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER]) - func() { - r.cltLock.Lock() - defer r.cltLock.Unlock() - err = r.facadeBasedRegistry.CreatePath(dubboPath) - }() + if f != nil { + err = f(dubboPath) + } if err != nil { logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%#v}", dubboPath, perrors.WithStack(err)) return "", "", perrors.WithMessagef(err, "facadeBasedRegistry.CreatePath(path:%s)", dubboPath) @@ -274,7 +334,7 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values) (string } // consumerRegistry for consumer role do -func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values) (string, string, error) { +func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values, f createPathFunc) (string, string, error) { var ( dubboPath string rawURL string @@ -282,23 +342,18 @@ func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values) (string ) dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.CONSUMER]) - func() { - r.cltLock.Lock() - defer r.cltLock.Unlock() - err = r.facadeBasedRegistry.CreatePath(dubboPath) - - }() + if f != nil { + err = f(dubboPath) + } if err != nil { logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) return "", "", perrors.WithStack(err) } dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), common.DubboNodes[common.PROVIDER]) - func() { - r.cltLock.Lock() - defer r.cltLock.Unlock() - err = r.facadeBasedRegistry.CreatePath(dubboPath) - }() + if f != nil { + err = f(dubboPath) + } if err != nil { logger.Errorf("facadeBasedRegistry.CreatePath(path{%s}) = error{%v}", dubboPath, perrors.WithStack(err)) @@ -323,20 +378,20 @@ func sleepWait(n int) { } // Subscribe :subscribe from registry, event will notify by notifyListener -func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) { +func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) error { n := 0 for { n++ if !r.IsAvailable() { logger.Warnf("event listener game over.") - return + return perrors.New("BaseRegistry is not available.") } listener, err := r.facadeBasedRegistry.DoSubscribe(url) if err != nil { if !r.IsAvailable() { logger.Warnf("event listener game over.") - return + return err } logger.Warnf("getListener() = err:%v", perrors.WithStack(err)) time.Sleep(time.Duration(RegistryConnDelay) * time.Second) @@ -358,6 +413,37 @@ func (r *BaseRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) } } +// UnSubscribe URL +func (r *BaseRegistry) UnSubscribe(url *common.URL, notifyListener NotifyListener) error { + if !r.IsAvailable() { + logger.Warnf("event listener game over.") + return perrors.New("BaseRegistry is not available.") + } + + listener, err := r.facadeBasedRegistry.DoUnsubscribe(url) + if err != nil { + if !r.IsAvailable() { + logger.Warnf("event listener game over.") + return perrors.New("BaseRegistry is not available.") + } + logger.Warnf("getListener() = err:%v", perrors.WithStack(err)) + return perrors.WithStack(err) + } + + for { + if serviceEvent, err := listener.Next(); err != nil { + logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err)) + listener.Close() + break + } else { + logger.Infof("update begin, service event: %v", serviceEvent.String()) + notifyListener.Notify(serviceEvent) + } + + } + return nil +} + // closeRegisters close and remove registry client and reset services map func (r *BaseRegistry) closeRegisters() { logger.Infof("begin to close provider client") diff --git a/registry/consul/registry.go b/registry/consul/registry.go index 4ef87394687aecc8804b2cebedd58fc0e72e8e6e..c425c5ec20d36be02c00499340f13b13c9aa2655 100644 --- a/registry/consul/registry.go +++ b/registry/consul/registry.go @@ -73,7 +73,8 @@ func newConsulRegistry(url *common.URL) (registry.Registry, error) { return r, nil } -// Register service to consul registry center +// Register register @url +// it delegate the job to register() method func (r *consulRegistry) Register(url common.URL) error { var err error @@ -87,6 +88,7 @@ func (r *consulRegistry) Register(url common.URL) error { return nil } +// register actually register the @url func (r *consulRegistry) register(url common.URL) error { service, err := buildService(url) if err != nil { @@ -95,8 +97,9 @@ func (r *consulRegistry) register(url common.URL) error { return r.client.Agent().ServiceRegister(service) } -// Unregister service from consul registry center -func (r *consulRegistry) Unregister(url common.URL) error { +// UnRegister unregister the @url +// it delegate the job to unregister() method +func (r *consulRegistry) UnRegister(url common.URL) error { var err error role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) @@ -109,18 +112,27 @@ func (r *consulRegistry) Unregister(url common.URL) error { return nil } +// unregister actually unregister the @url func (r *consulRegistry) unregister(url common.URL) error { return r.client.Agent().ServiceDeregister(buildId(url)) } -// Subscribe service from consul registry center -func (r *consulRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) { +// Subscribe subscribe the @url with the @notifyListener +func (r *consulRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) error { role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) if role == common.CONSUMER { r.subscribe(url, notifyListener) } + return nil +} + +// UnSubscribe is not supported yet +func (r *consulRegistry) UnSubscribe(url *common.URL, notifyListener registry.NotifyListener) error { + return perrors.New("UnSubscribe not support in consulRegistry") } +// subscribe actually subscribe the @url +// it loops forever until success func (r *consulRegistry) subscribe(url *common.URL, notifyListener registry.NotifyListener) { for { if !r.IsAvailable() { diff --git a/registry/consul/registry_test.go b/registry/consul/registry_test.go index bb6842cd8fb67dd2cc70b1a7530fbb94f618a9b0..94718f5ab657c198882f065a50e5d5a2c9d4bc6f 100644 --- a/registry/consul/registry_test.go +++ b/registry/consul/registry_test.go @@ -44,7 +44,7 @@ func (suite *consulRegistryTestSuite) testRegister() { func (suite *consulRegistryTestSuite) testUnregister() { consulProviderRegistry, _ := suite.providerRegistry.(*consulRegistry) - err := consulProviderRegistry.Unregister(suite.providerUrl) + err := consulProviderRegistry.UnRegister(suite.providerUrl) assert.NoError(suite.t, err) } diff --git a/registry/consul/utils_test.go b/registry/consul/utils_test.go index d66600b773ee78b43ac3da4edf8849d0019c744d..327dd95f7181907f6635c7fe89ef726bdcef5204 100644 --- a/registry/consul/utils_test.go +++ b/registry/consul/utils_test.go @@ -19,24 +19,19 @@ package consul import ( "fmt" - "io/ioutil" "net" "net/url" - "os" "strconv" "sync" "testing" ) -import ( - "github.com/hashicorp/consul/agent" -) - import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/registry" "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/consul" ) var ( @@ -51,71 +46,39 @@ var ( ) func newProviderRegistryUrl(host string, port int) *common.URL { - url1 := common.NewURLWithOptions( + return common.NewURLWithOptions( common.WithIp(host), common.WithPort(strconv.Itoa(port)), common.WithParams(url.Values{}), common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)), ) - return url1 } func newConsumerRegistryUrl(host string, port int) *common.URL { - url1 := common.NewURLWithOptions( + return common.NewURLWithOptions( common.WithIp(host), common.WithPort(strconv.Itoa(port)), common.WithParams(url.Values{}), common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)), ) - return url1 } func newProviderUrl(host string, port int, service string, protocol string) common.URL { - url1 := common.NewURLWithOptions( + return *common.NewURLWithOptions( common.WithIp(host), common.WithPort(strconv.Itoa(port)), common.WithPath(service), common.WithProtocol(protocol), ) - return *url1 } func newConsumerUrl(host string, port int, service string, protocol string) common.URL { - url1 := common.NewURLWithOptions( + return *common.NewURLWithOptions( common.WithIp(host), common.WithPort(strconv.Itoa(port)), common.WithPath(service), common.WithProtocol(protocol), ) - return *url1 -} - -type testConsulAgent struct { - dataDir string - testAgent *agent.TestAgent -} - -func newConsulAgent(t *testing.T, port int) *testConsulAgent { - dataDir, _ := ioutil.TempDir("./", "agent") - hcl := ` - ports { - http = ` + strconv.Itoa(port) + ` - } - data_dir = "` + dataDir + `" - ` - testAgent := &agent.TestAgent{Name: t.Name(), DataDir: dataDir, HCL: hcl} - testAgent.Start(t) - - consulAgent := &testConsulAgent{ - dataDir: dataDir, - testAgent: testAgent, - } - return consulAgent -} - -func (consulAgent *testConsulAgent) close() { - consulAgent.testAgent.Shutdown() - os.RemoveAll(consulAgent.dataDir) } type testServer struct { @@ -184,8 +147,8 @@ func (suite *consulRegistryTestSuite) close() { // register -> subscribe -> unregister func test1(t *testing.T) { - consulAgent := newConsulAgent(t, registryPort) - defer consulAgent.close() + consulAgent := consul.NewConsulAgent(t, registryPort) + defer consulAgent.Close() server := newServer(providerHost, providerPort) defer server.close() @@ -204,8 +167,8 @@ func test1(t *testing.T) { // subscribe -> register func test2(t *testing.T) { - consulAgent := newConsulAgent(t, registryPort) - defer consulAgent.close() + consulAgent := consul.NewConsulAgent(t, registryPort) + defer consulAgent.Close() server := newServer(providerHost, providerPort) defer server.close() diff --git a/registry/directory/directory.go b/registry/directory/directory.go index e845db01f1b8f76897f2beeaee45a84537c96d83..2fbf9410f76c473362964c9ef148e3c581d3d045 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -54,7 +54,7 @@ type RegistryDirectory struct { listenerLock sync.Mutex serviceType string registry registry.Registry - cacheInvokersMap *sync.Map //use sync.map + cacheInvokersMap *sync.Map // use sync.map cacheOriginUrl *common.URL configurators []config_center.Configurator consumerConfigurationListener *consumerConfigurationListener @@ -108,15 +108,15 @@ func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) { url *common.URL oldInvoker protocol.Invoker = nil ) - //judge is override or others + // judge is override or others if res != nil { url = &res.Service - //1.for override url in 2.6.x + // 1.for override url in 2.6.x if url.Protocol == constant.OVERRIDE_PROTOCOL || url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.CONFIGURATORS_CATEGORY { dir.configurators = append(dir.configurators, extension.GetDefaultConfigurator(url)) url = nil - } else if url.Protocol == constant.ROUTER_PROTOCOL || //2.for router + } else if url.Protocol == constant.ROUTER_PROTOCOL || // 2.for router url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY { url = nil @@ -126,7 +126,7 @@ func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) { logger.Infof("selector add service url{%s}", res.Service) var urls []*common.URL - for _, v := range directory.GetRouterURLSet().Values() { + for _, v := range config.GetRouterURLSet().Values() { urls = append(urls, v.(*common.URL)) } @@ -177,7 +177,7 @@ func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker { } groupInvokersList := make([]protocol.Invoker, 0, len(groupInvokersMap)) if len(groupInvokersMap) == 1 { - //len is 1 it means no group setting ,so do not need cluster again + // len is 1 it means no group setting ,so do not need cluster again for _, invokers := range groupInvokersMap { groupInvokersList = invokers } @@ -221,7 +221,7 @@ func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { logger.Error("URL is nil ,pls check if service url is subscribe successfully!") return nil } - //check the url's protocol is equal to the protocol which is configured in reference config or referenceUrl is not care about protocol + // check the url's protocol is equal to the protocol which is configured in reference config or referenceUrl is not care about protocol if url.Protocol == referenceUrl.Protocol || referenceUrl.Protocol == "" { newUrl := common.MergeUrl(url, referenceUrl) dir.overrideUrl(newUrl) @@ -271,7 +271,7 @@ func (dir *RegistryDirectory) IsAvailable() bool { // Destroy method func (dir *RegistryDirectory) Destroy() { - //TODO:unregister & unsubscribe + // TODO:unregister & unsubscribe dir.BaseDirectory.Destroy(func() { invokers := dir.cacheInvokers dir.cacheInvokers = []protocol.Invoker{} diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go index ac3f7124c12788959f529193b871652085fe6303..f2b2f8edd2d46950d2e74733b1d869e0de282ec0 100644 --- a/registry/directory/directory_test.go +++ b/registry/directory/directory_test.go @@ -45,7 +45,11 @@ import ( ) func init() { - config.SetConsumerConfig(config.ConsumerConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) + config.SetConsumerConfig(config.ConsumerConfig{ + BaseConfig: config.BaseConfig{ + ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}, + }, + }) } func TestSubscribe(t *testing.T) { @@ -60,7 +64,7 @@ func TestSubscribe(t *testing.T) { // registryDirectory, mockRegistry := normalRegistryDir() // time.Sleep(1e9) // assert.Len(t, registryDirectory.cacheInvokers, 3) -// mockRegistry.MockEvent(®istry.ServiceEvent{Action: remoting.EventTypeDel, Service: *common.NewURLWithOptions(common.WithPath("TEST0"), common.WithProtocol("dubbo"))}) +// mockRegistry.MockEvent(®istry.ServiceEvent{Action: remoting.EventTypeDel, Service: *event.NewURLWithOptions(event.WithPath("TEST0"), event.WithProtocol("dubbo"))}) // time.Sleep(1e9) // assert.Len(t, registryDirectory.cacheInvokers, 2) //} diff --git a/registry/etcdv3/registry.go b/registry/etcdv3/registry.go index f3df78177bda2b068d0ad88156b593ab3d48c5d7..2fec8eaad25e716fc5ed5ee33775d8898cb212e2 100644 --- a/registry/etcdv3/registry.go +++ b/registry/etcdv3/registry.go @@ -116,6 +116,11 @@ func (r *etcdV3Registry) DoRegister(root string, node string) error { return r.client.Create(path.Join(root, node), "") } +// nolint +func (r *etcdV3Registry) DoUnregister(root string, node string) error { + return perrors.New("DoUnregister is not support in etcdV3Registry") +} + // CloseAndNilClient closes listeners and clear client func (r *etcdV3Registry) CloseAndNilClient() { r.client.Close() @@ -174,3 +179,7 @@ func (r *etcdV3Registry) DoSubscribe(svc *common.URL) (registry.Listener, error) return configListener, nil } + +func (r *etcdV3Registry) DoUnsubscribe(conf *common.URL) (registry.Listener, error) { + return nil, perrors.New("DoUnsubscribe is not support in etcdV3Registry") +} diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..10396049fb6bb7a5a5935ce21639dc5a78a56b0b --- /dev/null +++ b/registry/etcdv3/service_discovery.go @@ -0,0 +1,322 @@ +/* + * 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 etcdv3 + +import ( + "fmt" + "sync" + "time" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" + gxpage "github.com/dubbogo/gost/page" + "github.com/hashicorp/vault/helper/jsonutil" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/etcdv3" +) + +const ( + ROOT = "/services" +) + +var ( + initLock sync.Mutex +) + +func init() { + extension.SetServiceDiscovery(constant.ETCDV3_KEY, newEtcdV3ServiceDiscovery) +} + +// new etcd service discovery struct +type etcdV3ServiceDiscovery struct { + // descriptor is a short string about the basic information of this instance + descriptor string + // client is current Etcdv3 client + client *etcdv3.Client + // serviceInstance is current serviceInstance + serviceInstance *registry.ServiceInstance + // services is when register or update will add service name + services *gxset.HashSet + // child listener + childListenerMap map[string]*etcdv3.EventListener +} + +// basic information of this instance +func (e *etcdV3ServiceDiscovery) String() string { + return e.descriptor +} + +// Destory service discovery +func (e *etcdV3ServiceDiscovery) Destroy() error { + if e.client != nil { + e.client.Close() + } + return nil +} + +// Register will register an instance of ServiceInstance to registry +func (e *etcdV3ServiceDiscovery) Register(instance registry.ServiceInstance) error { + + e.serviceInstance = &instance + + path := toPath(instance) + + if nil != e.client { + ins, err := jsonutil.EncodeJSON(instance) + if err == nil { + err = e.client.RegisterTemp(path, string(ins)) + if err != nil { + logger.Errorf("cannot register the instance: %s", string(ins), err) + } else { + e.services.Add(instance.GetServiceName()) + } + } + } + + return nil +} + +// Update will update the data of the instance in registry +func (e *etcdV3ServiceDiscovery) Update(instance registry.ServiceInstance) error { + path := toPath(instance) + + if nil != e.client { + ins, err := jsonutil.EncodeJSON(instance) + if nil == err { + e.client.RegisterTemp(path, string(ins)) + e.services.Add(instance.GetServiceName()) + } + } + + return nil +} + +// Unregister will unregister this instance from registry +func (e *etcdV3ServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + path := toPath(instance) + + if nil != e.client { + err := e.client.Delete(path) + e.services.Remove(instance.GetServiceName()) + e.serviceInstance = nil + return err + } + + return nil +} + +// ----------------- discovery ------------------- +// GetDefaultPageSize will return the default page size +func (e *etcdV3ServiceDiscovery) GetDefaultPageSize() int { + return registry.DefaultPageSize +} + +// GetServices will return the all service names. +func (e *etcdV3ServiceDiscovery) GetServices() *gxset.HashSet { + return e.services +} + +// GetInstances will return all service instances with serviceName +func (e *etcdV3ServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + + if nil != e.client { + // get keys and values + _, vList, err := e.client.GetChildrenKVList(toParentPath(serviceName)) + if nil == err { + serviceInstances := make([]registry.ServiceInstance, 0, len(vList)) + for _, v := range vList { + instance := ®istry.DefaultServiceInstance{} + err = jsonutil.DecodeJSON([]byte(v), &instance) + if nil == err { + serviceInstances = append(serviceInstances, instance) + } + } + return serviceInstances + } + logger.Infof("could not getChildrenKVList the err is:%v", err) + } + + return make([]registry.ServiceInstance, 0, 0) +} + +// GetInstancesByPage will return a page containing instances of ServiceInstance with the serviceName +// the page will start at offset +func (e *etcdV3ServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + + all := e.GetInstances(serviceName) + + res := make([]interface{}, 0, pageSize) + + for i := offset; i < len(all) && i < offset+pageSize; i++ { + res = append(res, all[i]) + } + + return gxpage.New(offset, pageSize, res, len(all)) +} + +// GetHealthyInstancesByPage will return a page containing instances of ServiceInstance. +// The param healthy indices that the instance should be healthy or not. +// The page will start at offset +func (e *etcdV3ServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + all := e.GetInstances(serviceName) + res := make([]interface{}, 0, pageSize) + + var ( + i = offset + count = 0 + ) + for i < len(all) && count < pageSize { + ins := all[i] + if ins.IsHealthy() == healthy { + res = append(res, all[i]) + count++ + } + i++ + } + return gxpage.New(offset, pageSize, res, len(all)) +} + +// Batch get all instances by the specified service names +func (e *etcdV3ServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { + res := make(map[string]gxpage.Pager, len(serviceNames)) + for _, name := range serviceNames { + res[name] = e.GetInstancesByPage(name, offset, requestedSize) + } + return res +} + +// ----------------- event ---------------------- +// AddListener adds a new ServiceInstancesChangedListener +// see addServiceInstancesChangedListener in Java +func (e *etcdV3ServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + return e.registerSreviceWatcher(listener.ServiceName) +} + +// DispatchEventByServiceName dispatches the ServiceInstancesChangedEvent to service instance whose name is serviceName +func (e *etcdV3ServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return e.DispatchEventForInstances(serviceName, e.GetInstances(serviceName)) +} + +// DispatchEventForInstances dispatches the ServiceInstancesChangedEvent to target instances +func (e *etcdV3ServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return e.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances)) +} + +// DispatchEvent dispatches the event +func (e *etcdV3ServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + extension.GetGlobalDispatcher().Dispatch(event) + return nil +} + +// Convert instance to dubbo path +func toPath(instance registry.ServiceInstance) string { + if instance == nil { + return "" + } + // like: /services/servicename1/host(127.0.0.1)/8080 + return fmt.Sprintf("%s%d", ROOT+constant.PATH_SEPARATOR+instance.GetServiceName()+constant.PATH_SEPARATOR+instance.GetHost()+constant.KEY_SEPARATOR, instance.GetPort()) +} + +// to dubbo service path +func toParentPath(serviceName string) string { + return ROOT + constant.PATH_SEPARATOR + serviceName +} + +// register service watcher +func (e *etcdV3ServiceDiscovery) registerSreviceWatcher(serviceName string) error { + + initLock.Lock() + defer initLock.Unlock() + + path := toParentPath(serviceName) + + listener, found := e.childListenerMap[serviceName] + + if !found { + listener = etcdv3.NewEventListener(e.client) + e.childListenerMap[serviceName] = listener + } + + listener.ListenServiceEvent(path, e) + + return nil +} + +// when child data change should DispatchEventByServiceName +func (e *etcdV3ServiceDiscovery) DataChange(eventType remoting.Event) bool { + + if eventType.Action == remoting.EventTypeUpdate { + instance := ®istry.DefaultServiceInstance{} + err := jsonutil.DecodeJSON([]byte(eventType.Content), &instance) + if err != nil { + instance.ServiceName = "" + } + + if err := e.DispatchEventByServiceName(instance.ServiceName); err != nil { + return false + } + } + + return true +} + +// netEcdv3ServiceDiscovery +func newEtcdV3ServiceDiscovery(name string) (registry.ServiceDiscovery, error) { + + initLock.Lock() + defer initLock.Unlock() + + sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name) + if !ok || len(sdc.RemoteRef) == 0 { + return nil, perrors.New("could not init the etcd service instance because the config is invalid") + } + + remoteConfig, ok := config.GetBaseConfig().GetRemoteConfig(sdc.RemoteRef) + if !ok { + return nil, perrors.New("could not find the remote config for name: " + sdc.RemoteRef) + } + + // init etcdv3 client + timeout, err := time.ParseDuration(remoteConfig.TimeoutStr) + if err != nil { + logger.Errorf("timeout config %v is invalid,err is %v", remoteConfig.TimeoutStr, err.Error()) + return nil, perrors.WithMessagef(err, "new etcd service discovery(address:%v)", remoteConfig.Address) + } + + logger.Infof("etcd address is: %v,timeout is:%s", remoteConfig.Address, timeout.String()) + + client := etcdv3.NewServiceDiscoveryClient( + etcdv3.WithName(etcdv3.RegistryETCDV3Client), + etcdv3.WithTimeout(timeout), + etcdv3.WithEndpoints(remoteConfig.Address), + ) + + descriptor := fmt.Sprintf("etcd-service-discovery[%s]", remoteConfig.Address) + + return &etcdV3ServiceDiscovery{descriptor, client, nil, gxset.NewSet(), make(map[string]*etcdv3.EventListener, 0)}, nil +} diff --git a/registry/etcdv3/service_discovery_test.go b/registry/etcdv3/service_discovery_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d8e3f1a2864150cc1f1e8996bc7c53e115dbef45 --- /dev/null +++ b/registry/etcdv3/service_discovery_test.go @@ -0,0 +1,80 @@ +/* + * 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 etcdv3 + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/registry" +) + +var testName = "test" + +func setUp() { + config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{ + Protocol: "etcdv3", + RemoteRef: testName, + } + + config.GetBaseConfig().Remotes[testName] = &config.RemoteConfig{ + Address: "localhost:2379", + TimeoutStr: "10s", + } +} + +func Test_newEtcdV3ServiceDiscovery(t *testing.T) { + name := constant.ETCDV3_KEY + _, err := newEtcdV3ServiceDiscovery(name) + + // warn: log configure file name is nil + assert.NotNil(t, err) + + sdc := &config.ServiceDiscoveryConfig{ + Protocol: "etcdv3", + RemoteRef: "mock", + } + config.GetBaseConfig().ServiceDiscoveries[name] = sdc + + _, err = newEtcdV3ServiceDiscovery(name) + + // RemoteConfig not found + assert.NotNil(t, err) + + config.GetBaseConfig().Remotes["mock"] = &config.RemoteConfig{ + Address: "localhost:2379", + TimeoutStr: "10s", + } + + res, err := newEtcdV3ServiceDiscovery(name) + assert.Nil(t, err) + assert.NotNil(t, res) +} + +func TestEtcdV3ServiceDiscovery_GetDefaultPageSize(t *testing.T) { + setUp() + serviceDiscovry := &etcdV3ServiceDiscovery{} + assert.Equal(t, registry.DefaultPageSize, serviceDiscovry.GetDefaultPageSize()) +} diff --git a/registry/event.go b/registry/event.go index 5fe6df6a379e1de8662917fb76c6d16fa9a17f37..39fb00c740ef2e70e2cd6768fa4a4bb3226f832c 100644 --- a/registry/event.go +++ b/registry/event.go @@ -25,6 +25,7 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/observer" "github.com/apache/dubbo-go/remoting" ) @@ -47,47 +48,9 @@ func (e ServiceEvent) String() string { return fmt.Sprintf("ServiceEvent{Action{%s}, Path{%s}}", e.Action, e.Service) } -// Event is align with Event interface in Java. -// it's the top abstraction -// Align with 2.7.5 -type Event interface { - fmt.Stringer - GetSource() interface{} - GetTimestamp() time.Time -} - -// baseEvent is the base implementation of Event -// You should never use it directly -type baseEvent struct { - source interface{} - timestamp time.Time -} - -// GetSource return the source -func (b *baseEvent) GetSource() interface{} { - return b.source -} - -// GetTimestamp return the timestamp when the event is created -func (b *baseEvent) GetTimestamp() time.Time { - return b.timestamp -} - -// String return a human readable string representing this event -func (b *baseEvent) String() string { - return fmt.Sprintf("baseEvent[source = %#v]", b.source) -} - -func newBaseEvent(source interface{}) *baseEvent { - return &baseEvent{ - source: source, - timestamp: time.Now(), - } -} - // ServiceInstancesChangedEvent represents service instances make some changing type ServiceInstancesChangedEvent struct { - baseEvent + observer.BaseEvent ServiceName string Instances []ServiceInstance } @@ -100,9 +63,9 @@ func (s *ServiceInstancesChangedEvent) String() string { // NewServiceInstancesChangedEvent will create the ServiceInstanceChangedEvent instance func NewServiceInstancesChangedEvent(serviceName string, instances []ServiceInstance) *ServiceInstancesChangedEvent { return &ServiceInstancesChangedEvent{ - baseEvent: baseEvent{ - source: serviceName, - timestamp: time.Now(), + BaseEvent: observer.BaseEvent{ + Source: serviceName, + Timestamp: time.Now(), }, ServiceName: serviceName, Instances: instances, diff --git a/registry/event/customizable_service_instance_listener.go b/registry/event/customizable_service_instance_listener.go new file mode 100644 index 0000000000000000000000000000000000000000..07e84c1454df91d2038beb08abddbc46274623c9 --- /dev/null +++ b/registry/event/customizable_service_instance_listener.go @@ -0,0 +1,73 @@ +/* + * 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 event + +import ( + "reflect" + "sync" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" +) + +func init() { + extension.AddEventListener(GetCustomizableServiceInstanceListener) +} + +// customizableServiceInstanceListener is singleton +type customizableServiceInstanceListener struct { +} + +// GetPriority return priority 9999, +// 9999 is big enough to make sure it will be last invoked +func (c *customizableServiceInstanceListener) GetPriority() int { + return 9999 +} + +// OnEvent if the event is ServiceInstancePreRegisteredEvent +// it will iterate all ServiceInstanceCustomizer instances +// or it will do nothing +func (c *customizableServiceInstanceListener) OnEvent(e observer.Event) error { + if preRegEvent, ok := e.(*ServiceInstancePreRegisteredEvent); ok { + for _, cus := range extension.GetCustomizers() { + cus.Customize(preRegEvent.serviceInstance) + } + } + return nil +} + +// GetEventType will return ServiceInstancePreRegisteredEvent +func (c *customizableServiceInstanceListener) GetEventType() reflect.Type { + return reflect.TypeOf(&ServiceInstancePreRegisteredEvent{}) +} + +var ( + customizableServiceInstanceListenerInstance *customizableServiceInstanceListener + customizableServiceInstanceListenerOnce sync.Once +) + +// GetCustomizableServiceInstanceListener returns an instance +// if the instance was not initialized, we create one +func GetCustomizableServiceInstanceListener() observer.EventListener { + customizableServiceInstanceListenerOnce.Do(func() { + customizableServiceInstanceListenerInstance = &customizableServiceInstanceListener{} + }) + return customizableServiceInstanceListenerInstance +} diff --git a/registry/event/customizable_service_instance_listener_test.go b/registry/event/customizable_service_instance_listener_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1c81ece498b4864c3ea7f586d90052f3022627fc --- /dev/null +++ b/registry/event/customizable_service_instance_listener_test.go @@ -0,0 +1,76 @@ +/* + * 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 event + +import ( + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/registry" +) + +func TestGetCustomizableServiceInstanceListener(t *testing.T) { + + prepareMetadataServiceForTest() + + cus := GetCustomizableServiceInstanceListener() + + assert.Equal(t, 9999, cus.GetPriority()) + + extension.AddCustomizers(&mockCustomizer{}) + + err := cus.OnEvent(&mockEvent{}) + assert.Nil(t, err) + err = cus.OnEvent(NewServiceInstancePreRegisteredEvent("hello", createInstance())) + assert.Nil(t, err) + + tp := cus.GetEventType() + assert.NotNil(t, tp) +} + +type mockEvent struct { +} + +func (m *mockEvent) String() string { + panic("implement me") +} + +func (m *mockEvent) GetSource() interface{} { + panic("implement me") +} + +func (m *mockEvent) GetTimestamp() time.Time { + panic("implement me") +} + +type mockCustomizer struct { +} + +func (m *mockCustomizer) GetPriority() int { + return 0 +} + +func (m *mockCustomizer) Customize(instance registry.ServiceInstance) { +} diff --git a/registry/event/event_publishing_service_deiscovery_test.go b/registry/event/event_publishing_service_deiscovery_test.go new file mode 100644 index 0000000000000000000000000000000000000000..54752c03c0de598226270b27c8d7d0f3621d07d1 --- /dev/null +++ b/registry/event/event_publishing_service_deiscovery_test.go @@ -0,0 +1,190 @@ +/* + * 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 event + +import ( + "reflect" + "testing" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" + gxpage "github.com/dubbogo/gost/page" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + dispatcher2 "github.com/apache/dubbo-go/common/observer/dispatcher" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/mapping" + _ "github.com/apache/dubbo-go/metadata/service/inmemory" + "github.com/apache/dubbo-go/registry" +) + +func TestEventPublishingServiceDiscovery_DispatchEvent(t *testing.T) { + + // extension.SetMetadataService("local", inmemory.NewMetadataService) + + config.GetApplicationConfig().MetadataType = "local" + + extension.SetGlobalServiceNameMapping(func() mapping.ServiceNameMapping { + return &mockServiceNameMapping{} + }) + + dc := NewEventPublishingServiceDiscovery(&ServiceDiscoveryA{}) + tsd := &TestServiceDiscoveryDestroyingEventListener{ + BaseListener: observer.NewBaseListener(), + } + tsd.SetT(t) + tsi := &TestServiceInstancePreRegisteredEventListener{} + tsi.SetT(t) + extension.AddEventListener(func() observer.EventListener { + return tsd + }) + extension.AddEventListener(func() observer.EventListener { + return tsi + }) + extension.SetEventDispatcher("direct", dispatcher2.NewDirectEventDispatcher) + extension.SetAndInitGlobalDispatcher("direct") + err := dc.Destroy() + assert.Nil(t, err) + si := ®istry.DefaultServiceInstance{Id: "testServiceInstance"} + err = dc.Register(si) + assert.Nil(t, err) + +} + +type TestServiceDiscoveryDestroyingEventListener struct { + suite.Suite + observer.BaseListener +} + +func (tel *TestServiceDiscoveryDestroyingEventListener) OnEvent(e observer.Event) error { + e1, ok := e.(*ServiceDiscoveryDestroyingEvent) + assert.Equal(tel.T(), ok, true) + assert.Equal(tel.T(), "testServiceDiscovery", e1.GetOriginal().String()) + assert.Equal(tel.T(), "testServiceDiscovery", e1.GetServiceDiscovery().String()) + return nil +} + +func (tel *TestServiceDiscoveryDestroyingEventListener) GetPriority() int { + return -1 +} + +func (tel *TestServiceDiscoveryDestroyingEventListener) GetEventType() reflect.Type { + return reflect.TypeOf(ServiceDiscoveryDestroyingEvent{}) +} + +type TestServiceInstancePreRegisteredEventListener struct { + suite.Suite + observer.BaseListener +} + +func (tel *TestServiceInstancePreRegisteredEventListener) OnEvent(e observer.Event) error { + e1, ok := e.(*ServiceInstancePreRegisteredEvent) + assert.Equal(tel.T(), ok, true) + assert.Equal(tel.T(), "testServiceInstance", e1.getServiceInstance().GetId()) + return nil +} + +func (tel *TestServiceInstancePreRegisteredEventListener) GetPriority() int { + return -1 +} + +func (tel *TestServiceInstancePreRegisteredEventListener) GetEventType() reflect.Type { + return reflect.TypeOf(ServiceInstancePreRegisteredEvent{}) +} + +type ServiceDiscoveryA struct { +} + +// String return mockServiceDiscovery +func (msd *ServiceDiscoveryA) String() string { + return "testServiceDiscovery" +} + +// Destroy do nothing +func (msd *ServiceDiscoveryA) Destroy() error { + return nil +} + +func (msd *ServiceDiscoveryA) Register(instance registry.ServiceInstance) error { + return nil +} + +func (msd *ServiceDiscoveryA) Update(instance registry.ServiceInstance) error { + return nil +} + +func (msd *ServiceDiscoveryA) Unregister(instance registry.ServiceInstance) error { + return nil +} + +func (msd *ServiceDiscoveryA) GetDefaultPageSize() int { + return 1 +} + +func (msd *ServiceDiscoveryA) GetServices() *gxset.HashSet { + return nil +} + +func (msd *ServiceDiscoveryA) GetInstances(serviceName string) []registry.ServiceInstance { + return nil +} + +func (msd *ServiceDiscoveryA) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + return nil +} + +func (msd *ServiceDiscoveryA) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + return nil +} + +func (msd *ServiceDiscoveryA) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { + return nil +} + +func (msd *ServiceDiscoveryA) AddListener(listener *registry.ServiceInstancesChangedListener) error { + return nil +} + +func (msd *ServiceDiscoveryA) DispatchEventByServiceName(serviceName string) error { + return nil +} + +func (msd *ServiceDiscoveryA) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return nil +} + +func (msd *ServiceDiscoveryA) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + return nil +} + +type mockServiceNameMapping struct { +} + +func (m *mockServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error { + return nil +} + +func (m *mockServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) { + return gxset.NewSet("dubbo"), nil +} diff --git a/registry/event/event_publishing_service_discovery.go b/registry/event/event_publishing_service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..3ee2f4a44946065cdf7489abc391df41f251d810 --- /dev/null +++ b/registry/event/event_publishing_service_discovery.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 event + +import ( + gxset "github.com/dubbogo/gost/container/set" + gxpage "github.com/dubbogo/gost/page" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" +) + +// EventPublishingServiceDiscovery will enhance Service Discovery +// Publish some event about service discovery +type EventPublishingServiceDiscovery struct { + serviceDiscovery registry.ServiceDiscovery +} + +// NewEventPublishingServiceDiscovery is a constructor +func NewEventPublishingServiceDiscovery(serviceDiscovery registry.ServiceDiscovery) *EventPublishingServiceDiscovery { + return &EventPublishingServiceDiscovery{ + serviceDiscovery: serviceDiscovery, + } +} + +// String returns serviceDiscovery.String() +func (epsd *EventPublishingServiceDiscovery) String() string { + return epsd.serviceDiscovery.String() +} + +// Destroy delegate function +func (epsd *EventPublishingServiceDiscovery) Destroy() error { + f := func() error { + return epsd.serviceDiscovery.Destroy() + } + return epsd.executeWithEvents(NewServiceDiscoveryDestroyingEvent(epsd, epsd.serviceDiscovery), + f, NewServiceDiscoveryDestroyedEvent(epsd, epsd.serviceDiscovery)) +} + +// Register delegate function +func (epsd *EventPublishingServiceDiscovery) Register(instance registry.ServiceInstance) error { + f := func() error { + return epsd.serviceDiscovery.Register(instance) + } + return epsd.executeWithEvents(NewServiceInstancePreRegisteredEvent(epsd.serviceDiscovery, instance), + f, NewServiceInstanceRegisteredEvent(epsd.serviceDiscovery, instance)) + +} + +// Update returns the result of serviceDiscovery.Update +func (epsd *EventPublishingServiceDiscovery) Update(instance registry.ServiceInstance) error { + f := func() error { + return epsd.serviceDiscovery.Update(instance) + } + return epsd.executeWithEvents(nil, f, nil) +} + +// Unregister unregister the instance and drop ServiceInstancePreUnregisteredEvent and ServiceInstanceUnregisteredEvent +func (epsd *EventPublishingServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + f := func() error { + return epsd.serviceDiscovery.Unregister(instance) + } + return epsd.executeWithEvents(NewServiceInstancePreUnregisteredEvent(epsd.serviceDiscovery, instance), + f, NewServiceInstanceUnregisteredEvent(epsd.serviceDiscovery, instance)) +} + +// GetDefaultPageSize returns the result of serviceDiscovery.GetDefaultPageSize +func (epsd *EventPublishingServiceDiscovery) GetDefaultPageSize() int { + return epsd.serviceDiscovery.GetDefaultPageSize() +} + +// GetServices returns the result of serviceDiscovery.GetServices +func (epsd *EventPublishingServiceDiscovery) GetServices() *gxset.HashSet { + return epsd.serviceDiscovery.GetServices() +} + +// GetInstances returns the result of serviceDiscovery.GetInstances +func (epsd *EventPublishingServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + return epsd.serviceDiscovery.GetInstances(serviceName) +} + +// GetInstancesByPage returns the result of serviceDiscovery.GetInstancesByPage +func (epsd *EventPublishingServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + return epsd.serviceDiscovery.GetInstancesByPage(serviceName, offset, pageSize) +} + +// GetHealthyInstancesByPage returns the result of serviceDiscovery.GetHealthyInstancesByPage +func (epsd *EventPublishingServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + return epsd.serviceDiscovery.GetHealthyInstancesByPage(serviceName, offset, pageSize, healthy) +} + +// GetRequestInstances returns result from serviceDiscovery.GetRequestInstances +func (epsd *EventPublishingServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { + return epsd.serviceDiscovery.GetRequestInstances(serviceNames, offset, requestedSize) +} + +// AddListener add event listener +func (epsd *EventPublishingServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + extension.GetGlobalDispatcher().AddEventListener(listener) + return epsd.serviceDiscovery.AddListener(listener) +} + +// DispatchEventByServiceName pass serviceName to serviceDiscovery +func (epsd *EventPublishingServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return epsd.serviceDiscovery.DispatchEventByServiceName(serviceName) +} + +// DispatchEventForInstances pass params to serviceDiscovery +func (epsd *EventPublishingServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return epsd.serviceDiscovery.DispatchEventForInstances(serviceName, instances) +} + +// DispatchEvent pass the event to serviceDiscovery +func (epsd *EventPublishingServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + return epsd.serviceDiscovery.DispatchEvent(event) +} + +// executeWithEvents dispatch before event and after event if return error will dispatch exception event +func (epsd *EventPublishingServiceDiscovery) executeWithEvents(beforeEvent observer.Event, f func() error, afterEvent observer.Event) error { + globalDispatcher := extension.GetGlobalDispatcher() + if beforeEvent != nil { + globalDispatcher.Dispatch(beforeEvent) + } + if err := f(); err != nil { + globalDispatcher.Dispatch(NewServiceDiscoveryExceptionEvent(epsd, epsd.serviceDiscovery, err)) + return err + } + if afterEvent != nil { + globalDispatcher.Dispatch(afterEvent) + } + return nil +} + +// getMetadataService returns metadata service instance +func getMetadataService() (service.MetadataService, error) { + return extension.GetMetadataService(config.GetApplicationConfig().MetadataType) +} diff --git a/registry/event/log_event_listener.go b/registry/event/log_event_listener.go new file mode 100644 index 0000000000000000000000000000000000000000..0781a6d6db303ba3a1eb99b6b4c6d0743f9066b3 --- /dev/null +++ b/registry/event/log_event_listener.go @@ -0,0 +1,60 @@ +/* + * 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 event + +import ( + "reflect" + "sync" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/observer" +) + +func init() { + extension.AddEventListener(GetLogEventListener) +} + +// logEventListener is singleton +type logEventListener struct { +} + +func (l *logEventListener) GetPriority() int { + return 0 +} + +func (l *logEventListener) OnEvent(e observer.Event) error { + logger.Info("Event happen: " + e.String()) + return nil +} + +func (l *logEventListener) GetEventType() reflect.Type { + return reflect.TypeOf(&observer.BaseEvent{}) +} + +var logEventListenerInstance *logEventListener +var logEventListenerOnce sync.Once + +func GetLogEventListener() observer.EventListener { + logEventListenerOnce.Do(func() { + logEventListenerInstance = &logEventListener{} + }) + return logEventListenerInstance +} diff --git a/registry/event/log_event_listener_test.go b/registry/event/log_event_listener_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3136564687f74e4c5cebd13d135e097234b21284 --- /dev/null +++ b/registry/event/log_event_listener_test.go @@ -0,0 +1,32 @@ +/* + * 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 event + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestLogEventListener(t *testing.T) { + l := &logEventListener{} + assert.Equal(t, 0, l.GetPriority()) + assert.Nil(t, l.OnEvent(&ServiceDiscoveryDestroyedEvent{})) +} diff --git a/registry/event/metadata_service_url_params_customizer.go b/registry/event/metadata_service_url_params_customizer.go new file mode 100644 index 0000000000000000000000000000000000000000..6d8f99b327363c9a2d636079ef1f74e78d4e0184 --- /dev/null +++ b/registry/event/metadata_service_url_params_customizer.go @@ -0,0 +1,105 @@ +/* + * 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 event + +import ( + "encoding/json" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/registry" +) + +func init() { + exceptKeys := gxset.NewSet( + // remove APPLICATION_KEY because service name must be present + constant.APPLICATION_KEY, + // remove GROUP_KEY, always uses service name. + constant.GROUP_KEY, + // remove TIMESTAMP_KEY because it's nonsense + constant.TIMESTAMP_KEY) + extension.AddCustomizers(&metadataServiceURLParamsMetadataCustomizer{exceptKeys: exceptKeys}) + +} + +type metadataServiceURLParamsMetadataCustomizer struct { + exceptKeys *gxset.HashSet +} + +// GetPriority will return 0 so that it will be invoked in front of user defining Customizer +func (m *metadataServiceURLParamsMetadataCustomizer) GetPriority() int { + return 0 +} + +func (m *metadataServiceURLParamsMetadataCustomizer) Customize(instance registry.ServiceInstance) { + ms, err := getMetadataService() + if err != nil { + logger.Errorf("could not find the metadata service", err) + return + } + serviceName := constant.METADATA_SERVICE_NAME + // error always is nil + version, _ := ms.Version() + group := instance.GetServiceName() + urls, err := ms.GetExportedURLs(serviceName, group, version, constant.ANY_VALUE) + if err != nil || len(urls) == 0 { + logger.Info("could not find the exported urls", err) + return + } + ps := m.convertToParams(urls) + str, err := json.Marshal(ps) + if err != nil { + logger.Errorf("could not transfer the map to json", err) + return + } + instance.GetMetadata()[constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME] = string(str) +} + +func (m *metadataServiceURLParamsMetadataCustomizer) convertToParams(urls []interface{}) map[string]map[string]string { + + // usually there will be only one protocol + res := make(map[string]map[string]string, 1) + // those keys are useless + + for _, ui := range urls { + u, err := common.NewURL(ui.(string)) + if err != nil { + logger.Errorf("could not parse the string to url: %s", ui.(string), err) + continue + } + p := make(map[string]string, len(u.GetParams())) + for k, v := range u.GetParams() { + // we will ignore that + if m.exceptKeys.Contains(k) || len(v) == 0 || len(v[0]) == 0 { + continue + } + p[k] = v[0] + } + p[constant.PORT_KEY] = u.Port + res[u.Protocol] = p + } + return res +} diff --git a/registry/event/metadata_service_url_params_customizer_test.go b/registry/event/metadata_service_url_params_customizer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..98ae2df883f590f4c3e4b379bb5a0fcbe46d946c --- /dev/null +++ b/registry/event/metadata_service_url_params_customizer_test.go @@ -0,0 +1,124 @@ +/* + * 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 event + +import ( + "testing" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" +) + +func prepareMetadataServiceForTest() { + config.GetApplicationConfig().MetadataType = "mock" + extension.SetMetadataService("mock", func() (service.MetadataService, error) { + return &mockMetadataService{ + urls: []interface{}{"mock://localhost:8080?a=b"}, + }, nil + }) +} + +func TestMetadataServiceURLParamsMetadataCustomizer(t *testing.T) { + + prepareMetadataServiceForTest() + + msup := &metadataServiceURLParamsMetadataCustomizer{exceptKeys: gxset.NewSet()} + assert.Equal(t, 0, msup.GetPriority()) + + msup.Customize(createInstance()) +} + +func createInstance() registry.ServiceInstance { + ins := ®istry.DefaultServiceInstance{} + return ins +} + +type mockMetadataService struct { + urls []interface{} +} + +func (m *mockMetadataService) Reference() string { + panic("implement me") +} + +func (m *mockMetadataService) ServiceName() (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) UnexportURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) UnsubscribeURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + return m.urls, nil +} + +func (m *mockMetadataService) MethodMapper() map[string]string { + panic("implement me") +} + +func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { + res := make([]common.URL, 0, len(m.urls)) + for _, ui := range m.urls { + u, _ := common.NewURL(ui.(string)) + res = append(res, u) + } + return res, nil +} + +func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) Version() (string, error) { + return "1.0.0", nil +} diff --git a/registry/event/protocol_ports_metadata_customizer.go b/registry/event/protocol_ports_metadata_customizer.go new file mode 100644 index 0000000000000000000000000000000000000000..a58471c2bd5a2e1b7b4211e02f605763b2e72c9c --- /dev/null +++ b/registry/event/protocol_ports_metadata_customizer.go @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "encoding/json" + "strconv" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/registry" +) + +func init() { + extension.AddCustomizers(&ProtocolPortsMetadataCustomizer{}) +} + +// ProtocolPortsMetadataCustomizer will update the endpoints +type ProtocolPortsMetadataCustomizer struct { +} + +// GetPriority will return 0, which means it will be invoked at the beginning +func (p *ProtocolPortsMetadataCustomizer) GetPriority() int { + return 0 +} + +// Customize put the the string like [{"protocol": "dubbo", "port": 123}] into instance's metadata +func (p *ProtocolPortsMetadataCustomizer) Customize(instance registry.ServiceInstance) { + metadataService, err := getMetadataService() + if err != nil { + logger.Errorf("Could not init the MetadataService", err) + return + } + + // 4 is enough... we don't have many protocol + protocolMap := make(map[string]int, 4) + + list, err := metadataService.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + if err != nil || len(list) == 0 { + logger.Debugf("Could not find exported urls", err) + return + } + + for _, ui := range list { + u, err := common.NewURL(ui.(string)) + if err != nil || len(u.Protocol) == 0 { + logger.Errorf("the url string is invalid: %s", ui.(string), err) + continue + } + + port, err := strconv.Atoi(u.Port) + if err != nil { + logger.Errorf("Could not customize the metadata of port. ", err) + } + protocolMap[u.Protocol] = port + } + + instance.GetMetadata()[constant.SERVICE_INSTANCE_ENDPOINTS] = endpointsStr(protocolMap) +} + +// endpointsStr convert the map to json like [{"protocol": "dubbo", "port": 123}] +func endpointsStr(protocolMap map[string]int) string { + if len(protocolMap) == 0 { + return "" + } + + endpoints := make([]endpoint, 0, len(protocolMap)) + for k, v := range protocolMap { + endpoints = append(endpoints, endpoint{ + Port: v, + Protocol: k, + }) + } + + str, err := json.Marshal(endpoints) + if err != nil { + logger.Errorf("could not convert the endpoints to json", err) + return "" + } + return string(str) +} + +// nolint +type endpoint struct { + Port int `json:"port, omitempty"` + Protocol string `json:"protocol, omitempty"` +} diff --git a/registry/event/service_config_exported_event.go b/registry/event/service_config_exported_event.go new file mode 100644 index 0000000000000000000000000000000000000000..5ec027da3178f7aba066cdb1d684798e611953ea --- /dev/null +++ b/registry/event/service_config_exported_event.go @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "time" +) + +import ( + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/config" +) + +// ServiceConfigExportedEvent represents an service was exported +type ServiceConfigExportedEvent struct { + observer.BaseEvent + ServiceConfig *config.ServiceConfig +} + +// NewServiceConfigExportedEvent create an instance +func NewServiceConfigExportedEvent(serviceConfig *config.ServiceConfig) *ServiceConfigExportedEvent { + return &ServiceConfigExportedEvent{ + BaseEvent: observer.BaseEvent{ + Source: serviceConfig, + Timestamp: time.Now(), + }, + ServiceConfig: serviceConfig, + } +} diff --git a/registry/event/service_discovery_event.go b/registry/event/service_discovery_event.go new file mode 100644 index 0000000000000000000000000000000000000000..13afa1a6aa63a8ad0721692d7e969d3af882b8f5 --- /dev/null +++ b/registry/event/service_discovery_event.go @@ -0,0 +1,103 @@ +/* + * 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 event + +import ( + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/registry" +) + +// ServiceDiscoveryEvent means that something happens to service discovery instance +type ServiceDiscoveryEvent struct { + observer.BaseEvent + original registry.ServiceDiscovery +} + +// NewServiceDiscoveryEvent returns an instance +func NewServiceDiscoveryEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryEvent { + return &ServiceDiscoveryEvent{ + BaseEvent: *observer.NewBaseEvent(discovery), + original: original, + } +} + +// GetServiceDiscovery returns the event source +func (sde *ServiceDiscoveryEvent) GetServiceDiscovery() registry.ServiceDiscovery { + return sde.GetSource().(registry.ServiceDiscovery) +} + +// GetOriginal actually I think we can remove this method. +func (sde *ServiceDiscoveryEvent) GetOriginal() registry.ServiceDiscovery { + return sde.original +} + +// ServiceDiscoveryDestroyingEvent +// this event will be dispatched before service discovery be destroyed +type ServiceDiscoveryDestroyingEvent struct { + ServiceDiscoveryEvent +} + +// ServiceDiscoveryExceptionEvent +// this event will be dispatched when the error occur in service discovery +type ServiceDiscoveryExceptionEvent struct { + ServiceDiscoveryEvent + err error +} + +// ServiceDiscoveryInitializedEvent +// this event will be dispatched after service discovery initialize +type ServiceDiscoveryInitializedEvent struct { + ServiceDiscoveryEvent +} + +// ServiceDiscoveryInitializingEvent +// this event will be dispatched before service discovery initialize +type ServiceDiscoveryInitializingEvent struct { + ServiceDiscoveryEvent +} + +// ServiceDiscoveryDestroyedEvent +// this event will be dispatched after service discovery be destroyed +type ServiceDiscoveryDestroyedEvent struct { + ServiceDiscoveryEvent +} + +// NewServiceDiscoveryDestroyingEvent create a ServiceDiscoveryDestroyingEvent +func NewServiceDiscoveryDestroyingEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryDestroyingEvent { + return &ServiceDiscoveryDestroyingEvent{*NewServiceDiscoveryEvent(discovery, original)} +} + +// NewServiceDiscoveryExceptionEvent create a ServiceDiscoveryExceptionEvent +func NewServiceDiscoveryExceptionEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery, err error) *ServiceDiscoveryExceptionEvent { + return &ServiceDiscoveryExceptionEvent{*NewServiceDiscoveryEvent(discovery, original), err} +} + +// NewServiceDiscoveryInitializedEvent create a ServiceDiscoveryInitializedEvent +func NewServiceDiscoveryInitializedEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryInitializedEvent { + return &ServiceDiscoveryInitializedEvent{*NewServiceDiscoveryEvent(discovery, original)} +} + +// NewServiceDiscoveryInitializingEvent create a ServiceDiscoveryInitializingEvent +func NewServiceDiscoveryInitializingEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryInitializingEvent { + return &ServiceDiscoveryInitializingEvent{*NewServiceDiscoveryEvent(discovery, original)} +} + +// NewServiceDiscoveryDestroyedEvent create a ServiceDiscoveryDestroyedEvent +func NewServiceDiscoveryDestroyedEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryDestroyedEvent { + return &ServiceDiscoveryDestroyedEvent{*NewServiceDiscoveryEvent(discovery, original)} +} diff --git a/registry/event/service_instance_event.go b/registry/event/service_instance_event.go new file mode 100644 index 0000000000000000000000000000000000000000..d4f23c299a844f4aab25e7d656a2cb99692861d7 --- /dev/null +++ b/registry/event/service_instance_event.go @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/registry" +) + +// ServiceInstanceEvent means something happen to this ServiceInstance +// like register this service instance +type ServiceInstanceEvent struct { + observer.BaseEvent + serviceInstance registry.ServiceInstance +} + +// NewServiceInstanceEvent create a ServiceInstanceEvent +func NewServiceInstanceEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstanceEvent { + return &ServiceInstanceEvent{ + BaseEvent: *observer.NewBaseEvent(source), + serviceInstance: instance, + } +} + +// getServiceInstance return the service instance +func (sie *ServiceInstanceEvent) getServiceInstance() registry.ServiceInstance { + return sie.serviceInstance +} + +// ServiceInstancePreRegisteredEvent +// this event will be dispatched before service instance be registered +type ServiceInstancePreRegisteredEvent struct { + ServiceInstanceEvent +} + +// ServiceInstancePreUnregisteredEvent +// this event will be dispatched before service instance be unregistered +type ServiceInstancePreUnregisteredEvent struct { + ServiceInstanceEvent +} + +// ServiceInstanceRegisteredEvent +// this event will be dispatched after service instance be registered +type ServiceInstanceRegisteredEvent struct { + ServiceInstanceEvent +} + +// ServiceInstanceRegisteredEvent +// this event will be dispatched after service instance be unregistered +type ServiceInstanceUnregisteredEvent struct { + ServiceInstanceEvent +} + +// NewServiceInstancePreRegisteredEvent create a ServiceInstancePreRegisteredEvent +func NewServiceInstancePreRegisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstancePreRegisteredEvent { + return &ServiceInstancePreRegisteredEvent{*NewServiceInstanceEvent(source, instance)} +} + +// NewServiceInstancePreUnregisteredEvent create a ServiceInstancePreUnregisteredEvent +func NewServiceInstancePreUnregisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstancePreUnregisteredEvent { + return &ServiceInstancePreUnregisteredEvent{*NewServiceInstanceEvent(source, instance)} +} + +// NewServiceInstanceRegisteredEvent create a ServiceInstanceRegisteredEvent +func NewServiceInstanceRegisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstanceRegisteredEvent { + return &ServiceInstanceRegisteredEvent{*NewServiceInstanceEvent(source, instance)} +} + +// NewServiceInstanceUnregisteredEvent create a ServiceInstanceUnregisteredEvent +func NewServiceInstanceUnregisteredEvent(source interface{}, instance registry.ServiceInstance) *ServiceInstanceUnregisteredEvent { + return &ServiceInstanceUnregisteredEvent{*NewServiceInstanceEvent(source, instance)} +} diff --git a/registry/event/service_name_mapping_listener.go b/registry/event/service_name_mapping_listener.go new file mode 100644 index 0000000000000000000000000000000000000000..a4ac8b28db5a3778cf39eef98886e1825521aa44 --- /dev/null +++ b/registry/event/service_name_mapping_listener.go @@ -0,0 +1,89 @@ +/* + * 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 event + +import ( + "reflect" + "sync" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/metadata/mapping" +) + +func init() { + extension.AddEventListener(GetServiceNameMappingListener) +} + +// serviceNameMappingListener listen to service name mapping event +// usually it means that we exported some service +// it's a singleton +type serviceNameMappingListener struct { + nameMapping mapping.ServiceNameMapping +} + +// GetPriority return 3, which ensure that this listener will be invoked after log listener +func (s *serviceNameMappingListener) GetPriority() int { + return 3 +} + +// OnEvent only handle ServiceConfigExportedEvent +func (s *serviceNameMappingListener) OnEvent(e observer.Event) error { + if ex, ok := e.(*ServiceConfigExportedEvent); ok { + sc := ex.ServiceConfig + urls := sc.GetExportedUrls() + + for _, u := range urls { + err := s.nameMapping.Map(u.GetParam(constant.INTERFACE_KEY, ""), + u.GetParam(constant.GROUP_KEY, ""), + u.GetParam(constant.Version, ""), + u.Protocol) + if err != nil { + return perrors.WithMessage(err, "could not map the service: "+u.String()) + } + } + } + return nil +} + +// GetEventType return ServiceConfigExportedEvent +func (s *serviceNameMappingListener) GetEventType() reflect.Type { + return reflect.TypeOf(&ServiceConfigExportedEvent{}) +} + +var ( + serviceNameMappingListenerInstance *serviceNameMappingListener + serviceNameMappingListenerOnce sync.Once +) + +// GetServiceNameMappingListener returns an instance +func GetServiceNameMappingListener() observer.EventListener { + serviceNameMappingListenerOnce.Do(func() { + serviceNameMappingListenerInstance = &serviceNameMappingListener{ + nameMapping: extension.GetGlobalServiceNameMapping(), + } + }) + return serviceNameMappingListenerInstance +} diff --git a/registry/event/service_revision_customizer.go b/registry/event/service_revision_customizer.go new file mode 100644 index 0000000000000000000000000000000000000000..fd21e8f4c7a71cedfe1de7e9c836e7cee278182e --- /dev/null +++ b/registry/event/service_revision_customizer.go @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "fmt" + "hash/crc32" + "sort" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" +) + +const defaultRevision = "N/A" + +func init() { + extension.AddCustomizers(&exportedServicesRevisionMetadataCustomizer{}) + extension.AddCustomizers(&subscribedServicesRevisionMetadataCustomizer{}) +} + +type exportedServicesRevisionMetadataCustomizer struct { +} + +// GetPriority will return 1 so that it will be invoked in front of user defining Customizer +func (e *exportedServicesRevisionMetadataCustomizer) GetPriority() int { + return 1 +} + +// Customize calculate the revision for exported urls and then put it into instance metadata +func (e *exportedServicesRevisionMetadataCustomizer) Customize(instance registry.ServiceInstance) { + ms, err := getMetadataService() + if err != nil { + logger.Errorf("could not get metadata service", err) + return + } + + urls, err := ms.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + + if err != nil { + logger.Errorf("could not find the exported url", err) + } + + revision := resolveRevision(urls) + if len(revision) == 0 { + revision = defaultRevision + } + instance.GetMetadata()[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME] = revision +} + +type subscribedServicesRevisionMetadataCustomizer struct { +} + +// GetPriority will return 2 so that it will be invoked in front of user defining Customizer +func (e *subscribedServicesRevisionMetadataCustomizer) GetPriority() int { + return 2 +} + +// Customize calculate the revision for subscribed urls and then put it into instance metadata +func (e *subscribedServicesRevisionMetadataCustomizer) Customize(instance registry.ServiceInstance) { + ms, err := getMetadataService() + if err != nil { + logger.Errorf("could not get metadata service", err) + return + } + + urls, err := ms.GetSubscribedURLs() + + if err != nil { + logger.Errorf("could not find the subscribed url", err) + } + + revision := resolveRevision(service.ConvertURLArrToIntfArr(urls)) + if len(revision) == 0 { + revision = defaultRevision + } + instance.GetMetadata()[constant.SUBSCRIBED_SERVICES_REVISION_PROPERTY_NAME] = revision +} + +// resolveRevision is different from Dubbo because golang doesn't support overload +// so that we could use interface + method name as identifier and ignore the method params +// per my understanding, it's enough because Dubbo actually ignore the url params. +// please refer org.apache.dubbo.common.URL#toParameterString(java.lang.String...) +func resolveRevision(urls []interface{}) string { + if len(urls) == 0 { + return "" + } + candidates := make([]string, 0, len(urls)) + + for _, ui := range urls { + u, err := common.NewURL(ui.(string)) + if err != nil { + logger.Errorf("could not parse the string to URL structure") + continue + } + sk := u.GetParam(constant.INTERFACE_KEY, "") + + if len(u.Methods) == 0 { + candidates = append(candidates, sk) + } else { + for _, m := range u.Methods { + // methods are part of candidates + candidates = append(candidates, sk+constant.KEY_SEPARATOR+m) + } + } + + // append url params if we need it + } + sort.Sort(sort.StringSlice(candidates)) + + // it's nearly impossible to be overflow + res := uint64(0) + for _, c := range candidates { + res += uint64(crc32.ChecksumIEEE([]byte(c))) + } + return fmt.Sprint(res) +} diff --git a/registry/event_listener.go b/registry/event_listener.go index 1805f2833c96bd08c4cf9c92337d7d221e8829e9..9e9ec2d5d4bcb8d1af90fff73db1c6708427f7f7 100644 --- a/registry/event_listener.go +++ b/registry/event_listener.go @@ -18,26 +18,39 @@ package registry import ( - gxsort "github.com/dubbogo/gost/sort" + "reflect" ) -// EventListener is an new interface used to align with dubbo 2.7.5 -// It contains the Prioritized means that the listener has its priority -type EventListener interface { - gxsort.Prioritizer - // OnEvent handle this event - OnEvent(e Event) error +import ( + "github.com/apache/dubbo-go/common/observer" +) + +// The Service Discovery Changed Event Listener +type ServiceInstancesChangedListener struct { + ServiceName string + ChangedNotify observer.ChangedNotify } -// ConditionalEventListener only handle the event which it can handle -type ConditionalEventListener interface { - EventListener - // Accept will make the decision whether it should handle this event - Accept(e Event) bool +// OnEvent on ServiceInstancesChangedEvent the service instances change event +func (lstn *ServiceInstancesChangedListener) OnEvent(e observer.Event) error { + lstn.ChangedNotify.Notify(e) + return nil } -// ServiceInstancesChangedListener is used when the Service Discovery Changed -// TODO (implement ConditionalEventListener) -type ServiceInstancesChangedListener struct { - ServiceName string +// Accept return true if the name is the same +func (lstn *ServiceInstancesChangedListener) Accept(e observer.Event) bool { + if ce, ok := e.(*ServiceInstancesChangedEvent); ok { + return ce.ServiceName == lstn.ServiceName + } + return false +} + +// GetPriority returns -1, it will be the first invoked listener +func (lstn *ServiceInstancesChangedListener) GetPriority() int { + return -1 +} + +// GetEventType returns ServiceInstancesChangedEvent +func (lstn *ServiceInstancesChangedListener) GetEventType() reflect.Type { + return reflect.TypeOf(&ServiceInstancesChangedEvent{}) } diff --git a/registry/kubernetes/registry.go b/registry/kubernetes/registry.go index f06d80124b7a627954ca3f4de0ae189e424708fd..7c5162670d85f661fb8460cc69537ac9b7a12a23 100644 --- a/registry/kubernetes/registry.go +++ b/registry/kubernetes/registry.go @@ -114,6 +114,10 @@ func (r *kubernetesRegistry) DoRegister(root string, node string) error { return r.client.Create(path.Join(root, node), "") } +func (r *kubernetesRegistry) DoUnregister(root string, node string) error { + return perrors.New("DoUnregister is not support in kubernetesRegistry") +} + // DoSubscribe actually subscribe the provider URL func (r *kubernetesRegistry) DoSubscribe(svc *common.URL) (registry.Listener, error) { @@ -147,6 +151,11 @@ func (r *kubernetesRegistry) DoSubscribe(svc *common.URL) (registry.Listener, er return configListener, nil } +// nolint +func (r *kubernetesRegistry) DoUnsubscribe(conf *common.URL) (registry.Listener, error) { + return nil, perrors.New("DoUnsubscribe is not support in kubernetesRegistry") +} + // InitListeners init listeners of kubernetes registry center func (r *kubernetesRegistry) InitListeners() { r.listener = kubernetes.NewEventListener(r.client) diff --git a/registry/mock_registry.go b/registry/mock_registry.go index 2b83d5ab8892f673e1123cd01fa74e48e3d2dc22..10561d0f49e995c94c93fa0463fc0b0421ff6e20 100644 --- a/registry/mock_registry.go +++ b/registry/mock_registry.go @@ -51,6 +51,11 @@ func (*MockRegistry) Register(url common.URL) error { return nil } +// nolint +func (r *MockRegistry) UnRegister(conf common.URL) error { + return nil +} + // nolint func (r *MockRegistry) Destroy() { if r.destroyed.CAS(false, true) { @@ -72,7 +77,7 @@ func (r *MockRegistry) subscribe(*common.URL) (Listener, error) { } // nolint -func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) { +func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) error { go func() { for { if !r.IsAvailable() { @@ -104,6 +109,12 @@ func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) } } }() + return nil +} + +// UnSubscribe : +func (r *MockRegistry) UnSubscribe(url *common.URL, notifyListener NotifyListener) error { + return nil } type listener struct { diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index 3eeb7680abb3da98f5ed08f1aea57d490b2caf85..51d3e2f56abac8e4ab8b966870f1ff5bb79c4171 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -19,6 +19,7 @@ package nacos import ( "bytes" + "net" "strconv" "strings" "time" @@ -26,6 +27,9 @@ import ( import ( gxnet "github.com/dubbogo/gost/net" + "github.com/nacos-group/nacos-sdk-go/clients" + "github.com/nacos-group/nacos-sdk-go/clients/naming_client" + nacosConstant "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/vo" perrors "github.com/pkg/errors" ) @@ -43,7 +47,7 @@ var ( ) const ( - //RegistryConnDelay registry connection delay + // RegistryConnDelay registry connection delay RegistryConnDelay = 3 ) @@ -53,18 +57,8 @@ func init() { } type nacosRegistry struct { - nacosBaseRegistry -} - -// newNacosRegistry will create an instance -func newNacosRegistry(url *common.URL) (registry.Registry, error) { - base, err := newBaseRegistry(url) - if err != nil { - return nil, perrors.WithStack(err) - } - return &nacosRegistry{ - base, - }, nil + *common.URL + namingClient naming_client.INamingClient } func getCategory(url common.URL) string { @@ -137,23 +131,28 @@ func (nr *nacosRegistry) Register(url common.URL) error { return nil } +// UnRegister +func (nr *nacosRegistry) UnRegister(conf common.URL) error { + return perrors.New("UnRegister is not support in nacosRegistry") +} + func (nr *nacosRegistry) subscribe(conf *common.URL) (registry.Listener, error) { return NewNacosListener(*conf, nr.namingClient) } // subscribe from registry -func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) { +func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.NotifyListener) error { for { if !nr.IsAvailable() { logger.Warnf("event listener game over.") - return + return perrors.New("nacosRegistry is not available.") } listener, err := nr.subscribe(url) if err != nil { if !nr.IsAvailable() { logger.Warnf("event listener game over.") - return + return err } logger.Warnf("getListener() = err:%v", perrors.WithStack(err)) time.Sleep(time.Duration(RegistryConnDelay) * time.Second) @@ -165,7 +164,7 @@ func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.Noti if err != nil { logger.Warnf("Selector.watch() = error{%v}", perrors.WithStack(err)) listener.Close() - return + return err } logger.Infof("update begin, service event: %v", serviceEvent.String()) @@ -173,6 +172,12 @@ func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.Noti } } + return nil +} + +// UnSubscribe : +func (nr *nacosRegistry) UnSubscribe(url *common.URL, notifyListener registry.NotifyListener) error { + return perrors.New("UnSubscribe not support in nacosRegistry") } // GetUrl gets its registration URL @@ -182,6 +187,7 @@ func (nr *nacosRegistry) GetUrl() common.URL { // IsAvailable determines nacos registry center whether it is available func (nr *nacosRegistry) IsAvailable() bool { + // TODO return true } @@ -189,3 +195,62 @@ func (nr *nacosRegistry) IsAvailable() bool { func (nr *nacosRegistry) Destroy() { return } + +// newNacosRegistry will create new instance +func newNacosRegistry(url *common.URL) (registry.Registry, error) { + nacosConfig, err := getNacosConfig(url) + if err != nil { + return &nacosRegistry{}, err + } + client, err := clients.CreateNamingClient(nacosConfig) + if err != nil { + return &nacosRegistry{}, err + } + registry := &nacosRegistry{ + URL: url, + namingClient: client, + } + return registry, nil +} + +// getNacosConfig will return the nacos config +// TODO support RemoteRef +func getNacosConfig(url *common.URL) (map[string]interface{}, error) { + if url == nil { + return nil, perrors.New("url is empty!") + } + if len(url.Location) == 0 { + return nil, perrors.New("url.location is empty!") + } + configMap := make(map[string]interface{}, 2) + + addresses := strings.Split(url.Location, ",") + serverConfigs := make([]nacosConstant.ServerConfig, 0, len(addresses)) + for _, addr := range addresses { + ip, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, perrors.WithMessagef(err, "split [%s] ", addr) + } + port, _ := strconv.Atoi(portStr) + serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{ + IpAddr: ip, + Port: uint64(port), + }) + } + configMap["serverConfigs"] = serverConfigs + + var clientConfig nacosConstant.ClientConfig + timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + if err != nil { + return nil, err + } + clientConfig.TimeoutMs = uint64(timeout.Seconds() * 1000) + clientConfig.ListenInterval = 2 * clientConfig.TimeoutMs + clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "") + clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "") + clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "") + clientConfig.NotLoadCacheAtStart = true + configMap["clientConfig"] = clientConfig + + return configMap, nil +} diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go index 2611a8dc58d2a45e578d90aa6a5d1aeb7e7f4f63..63d92d70fd5e1a00f0ce1ca95b1926fb9c36c84b 100644 --- a/registry/nacos/service_discovery.go +++ b/registry/nacos/service_discovery.go @@ -17,26 +17,32 @@ package nacos +import ( + "fmt" + "sync" +) + import ( "github.com/dubbogo/gost/container/set" "github.com/dubbogo/gost/page" + "github.com/nacos-group/nacos-sdk-go/clients/naming_client" "github.com/nacos-group/nacos-sdk-go/model" "github.com/nacos-group/nacos-sdk-go/vo" perrors "github.com/pkg/errors" ) import ( - "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting/nacos" ) const ( - defaultGroup = "DEFAULT_GROUP" - idKey = "id" - defaultPageSize = 100 + defaultGroup = constant.SERVICE_DISCOVERY_DEFAULT_GROUP + idKey = "id" ) // init will put the service discovery into extension @@ -48,8 +54,12 @@ func init() { // There is a problem, the go client for nacos does not support the id field. // we will use the metadata to store the id of ServiceInstance type nacosServiceDiscovery struct { - nacosBaseRegistry group string + // descriptor is a short string about the basic information of this instance + descriptor string + + // namingClient is the Nacos' client + namingClient naming_client.INamingClient } // Destroy will close the service discovery. @@ -93,7 +103,7 @@ func (n *nacosServiceDiscovery) Unregister(instance registry.ServiceInstance) er // GetDefaultPageSize will return the constant registry.DefaultPageSize func (n *nacosServiceDiscovery) GetDefaultPageSize() int { - return defaultPageSize + return registry.DefaultPageSize } // GetServices will return the all services @@ -238,7 +248,7 @@ func (n *nacosServiceDiscovery) DispatchEventForInstances(serviceName string, in // DispatchEvent will dispatch the event func (n *nacosServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { - // TODO(waiting for event dispatcher, another task) + extension.GetGlobalDispatcher().Dispatch(event) return nil } @@ -255,10 +265,12 @@ func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInst Ip: instance.GetHost(), Port: uint64(instance.GetPort()), Metadata: metadata, - Enable: instance.IsEnable(), - Healthy: instance.IsHealthy(), - GroupName: n.group, - Ephemeral: true, + // We must specify the weight since Java nacos client will ignore the instance whose weight is 0 + Weight: 1, + Enable: instance.IsEnable(), + Healthy: instance.IsHealthy(), + GroupName: n.group, + Ephemeral: true, } } @@ -272,15 +284,58 @@ func (n *nacosServiceDiscovery) toDeregisterInstance(instance registry.ServiceIn } } -// toDeregisterInstance will create new service discovery instance -func newNacosServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) { +func (n *nacosServiceDiscovery) String() string { + return n.descriptor +} + +var ( + // 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition + instanceMap = make(map[string]registry.ServiceDiscovery, 16) + initLock sync.Mutex +) + +// newNacosServiceDiscovery will create new service discovery instance +// use double-check pattern to reduce race condition +func newNacosServiceDiscovery(name string) (registry.ServiceDiscovery, error) { + + instance, ok := instanceMap[name] + if ok { + return instance, nil + } + + initLock.Lock() + defer initLock.Unlock() + + // double check + instance, ok = instanceMap[name] + if ok { + return instance, nil + } + + sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name) + if !ok || len(sdc.RemoteRef) == 0 { + return nil, perrors.New("could not init the instance because the config is invalid") + } - base, err := newBaseRegistry(url) + remoteConfig, ok := config.GetBaseConfig().GetRemoteConfig(sdc.RemoteRef) + if !ok { + return nil, perrors.New("could not find the remote config for name: " + sdc.RemoteRef) + } + group := sdc.Group + if len(group) == 0 { + group = defaultGroup + } + + client, err := nacos.NewNacosClient(remoteConfig) if err != nil { - return nil, perrors.WithStack(err) + return nil, perrors.WithMessage(err, "create nacos client failed.") } + + descriptor := fmt.Sprintf("nacos-service-discovery[%s]", remoteConfig.Address) + return &nacosServiceDiscovery{ - nacosBaseRegistry: base, - group: url.GetParam(constant.NACOS_GROUP, defaultGroup), + group: group, + namingClient: client, + descriptor: descriptor, }, nil } diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 04431a614b40288b2a21f75d69c4be313bd7721f..4b069c2e82c614872bfe986fc911fc672f0efad1 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -18,8 +18,10 @@ package nacos import ( + "math/rand" "strconv" "testing" + "time" ) import ( @@ -27,23 +29,66 @@ import ( ) import ( - "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/common/observer/dispatcher" + "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/registry" ) +var ( + testName = "test" +) + +func Test_newNacosServiceDiscovery(t *testing.T) { + name := "nacos1" + _, err := newNacosServiceDiscovery(name) + + // the ServiceDiscoveryConfig not found + assert.NotNil(t, err) + + sdc := &config.ServiceDiscoveryConfig{ + Protocol: "nacos", + RemoteRef: "mock", + } + config.GetBaseConfig().ServiceDiscoveries[name] = sdc + + _, err = newNacosServiceDiscovery(name) + + // RemoteConfig not found + assert.NotNil(t, err) + + config.GetBaseConfig().Remotes["mock"] = &config.RemoteConfig{ + Address: "console.nacos.io:80", + TimeoutStr: "10s", + } + + res, err := newNacosServiceDiscovery(name) + assert.Nil(t, err) + assert.NotNil(t, res) + +} + func TestNacosServiceDiscovery_Destroy(t *testing.T) { - serviceDiscovry, err := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl()) + prepareData() + serviceDiscovery, err := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) assert.Nil(t, err) - assert.NotNil(t, serviceDiscovry) - err = serviceDiscovry.Destroy() + assert.NotNil(t, serviceDiscovery) + err = serviceDiscovery.Destroy() assert.Nil(t, err) - assert.Nil(t, serviceDiscovry.(*nacosServiceDiscovery).namingClient) + assert.Nil(t, serviceDiscovery.(*nacosServiceDiscovery).namingClient) } func TestNacosServiceDiscovery_CRUD(t *testing.T) { - serviceName := "service-name" + prepareData() + extension.SetEventDispatcher("mock", func() observer.EventDispatcher { + return &dispatcher.MockEventDispatcher{} + }) + + extension.SetAndInitGlobalDispatcher("mock") + rand.Seed(time.Now().Unix()) + serviceName := "service-name" + strconv.Itoa(rand.Intn(10000)) id := "id" host := "host" port := 123 @@ -59,7 +104,7 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { // clean data - serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl()) + serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) // clean data for local test serviceDiscovry.Unregister(®istry.DefaultServiceInstance{ @@ -71,7 +116,9 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { err := serviceDiscovry.Register(instance) assert.Nil(t, err) - + //sometimes nacos may be failed to push update of instance, + //so it need 10s to pull, we sleep 10 second to make sure instance has been update + time.Sleep(11 * time.Second) page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true) assert.NotNil(t, page) @@ -112,11 +159,19 @@ func TestNacosServiceDiscovery_CRUD(t *testing.T) { } func TestNacosServiceDiscovery_GetDefaultPageSize(t *testing.T) { - serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, mockUrl()) - assert.Equal(t, defaultPageSize, serviceDiscovry.GetDefaultPageSize()) + prepareData() + serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) + assert.Equal(t, registry.DefaultPageSize, serviceDiscovry.GetDefaultPageSize()) } -func mockUrl() *common.URL { - regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) - return ®url +func prepareData() { + config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{ + Protocol: "nacos", + RemoteRef: testName, + } + + config.GetBaseConfig().Remotes[testName] = &config.RemoteConfig{ + Address: "console.nacos.io:80", + TimeoutStr: "10s", + } } diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go index 15fd3cacfacad36309e0ad4deb3c7c7441e47e26..2d6e0248fddb88bfa5ce19546fb7aed703b0fd3c 100644 --- a/registry/protocol/protocol_test.go +++ b/registry/protocol/protocol_test.go @@ -42,7 +42,9 @@ import ( ) func init() { - config.SetProviderConfig(config.ProviderConfig{ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) + config.SetProviderConfig(config.ProviderConfig{BaseConfig: config.BaseConfig{ + ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}, + }}) } func referNormal(t *testing.T, regProtocol *registryProtocol) { @@ -66,8 +68,9 @@ func referNormal(t *testing.T, regProtocol *registryProtocol) { func TestRefer(t *testing.T) { config.SetConsumerConfig( - config.ConsumerConfig{ - ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}}) + config.ConsumerConfig{BaseConfig: config.BaseConfig{ + ApplicationConfig: &config.ApplicationConfig{Name: "test-application"}, + }}) regProtocol := newRegistryProtocol() referNormal(t, regProtocol) } diff --git a/registry/registry.go b/registry/registry.go index 5b37aa684ca90d1f18898b9f62f27d86a2c0fba3..bb09ead7ef2af6707345086f8695b35286d76a10 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -34,15 +34,28 @@ type Registry interface { //And it is also used for service consumer calling , register services cared about ,for dubbo's admin monitoring. Register(url common.URL) error + // UnRegister is required to support the contract: + // 1. If it is the persistent stored data of dynamic=false, the registration data can not be found, then the IllegalStateException is thrown, otherwise it is ignored. + // 2. Unregister according to the full url match. + // url Registration information , is not allowed to be empty, e.g: dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin + UnRegister(url common.URL) error + //When creating new registry extension,pls select one of the following modes. //Will remove in dubbogo version v1.1.0 //mode1 : return Listener with Next function which can return subscribe service event from registry //Deprecated! - //subscribe(common.URL) (Listener, error) + //subscribe(event.URL) (Listener, error) //Will relace mode1 in dubbogo version v1.1.0 //mode2 : callback mode, subscribe with notify(notify listener). - Subscribe(*common.URL, NotifyListener) + Subscribe(*common.URL, NotifyListener) error + + // UnSubscribe is required to support the contract: + // 1. If don't subscribe, ignore it directly. + // 2. Unsubscribe by full URL match. + // url Subscription condition, not allowed to be empty, e.g. consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin + // listener A listener of the change event, not allowed to be empty + UnSubscribe(*common.URL, NotifyListener) error } // nolint diff --git a/registry/service_discovery.go b/registry/service_discovery.go index 1d5a3593e392083d2115222e131974b941a391c3..cb7a3c0182ff88995ab9dd6c920523225c3cb36c 100644 --- a/registry/service_discovery.go +++ b/registry/service_discovery.go @@ -26,6 +26,8 @@ import ( gxpage "github.com/dubbogo/gost/page" ) +const DefaultPageSize = 100 + // ServiceDiscovery is the common operations of Service Discovery type ServiceDiscovery interface { fmt.Stringer diff --git a/registry/service_instance.go b/registry/service_instance.go index 247c8567659d1d512a6685ddb0404fecd9968bcd..dbb458284d48aa350f2d5d3408b187b437ac81cd 100644 --- a/registry/service_instance.go +++ b/registry/service_instance.go @@ -17,6 +17,10 @@ package registry +import ( + gxsort "github.com/dubbogo/gost/sort" +) + // ServiceInstance is the model class of an instance of a service, which is used for service registration and discovery. type ServiceInstance interface { @@ -84,7 +88,19 @@ func (d *DefaultServiceInstance) IsHealthy() bool { return d.Healthy } -// GetMetadata will return the metadata +// GetMetadata will return the metadata, it will never return nil func (d *DefaultServiceInstance) GetMetadata() map[string]string { + if d.Metadata == nil { + d.Metadata = make(map[string]string, 0) + } return d.Metadata } + +// ServiceInstanceCustomizer is an extension point which allow user using custom logic to modify instance +// Be careful of priority. Usually you should use number between [100, 9000] +// other number will be thought as system reserve number +type ServiceInstanceCustomizer interface { + gxsort.Prioritizer + + Customize(instance ServiceInstance) +} diff --git a/registry/servicediscovery/instance/random/random_service_instance_selector.go b/registry/servicediscovery/instance/random/random_service_instance_selector.go new file mode 100644 index 0000000000000000000000000000000000000000..3f8f30dc8e9e91f9c75f8ff0611c98bb2f0c7b85 --- /dev/null +++ b/registry/servicediscovery/instance/random/random_service_instance_selector.go @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package random + +import ( + "math/rand" + "time" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/registry/servicediscovery/instance" +) + +func init() { + extension.SetServiceInstanceSelector("random", NewRandomServiceInstanceSelector) +} + +//the ServiceInstanceSelector implementation based on Random algorithm +type RandomServiceInstanceSelector struct { +} + +func NewRandomServiceInstanceSelector() instance.ServiceInstanceSelector { + return &RandomServiceInstanceSelector{} +} + +func (r *RandomServiceInstanceSelector) Select(url common.URL, serviceInstances []registry.ServiceInstance) registry.ServiceInstance { + if len(serviceInstances) == 0 { + return nil + } + if len(serviceInstances) == 1 { + return serviceInstances[0] + } + rand.Seed(time.Now().UnixNano()) + index := rand.Intn(len(serviceInstances)) + return serviceInstances[index] + +} diff --git a/registry/servicediscovery/instance/random/random_service_instance_selector_test.go b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cddeb42c904131cdc6a62e5142de850410a3ec5a --- /dev/null +++ b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package random + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/registry" +) + +func TestRandomServiceInstanceSelector_Select(t *testing.T) { + selector := NewRandomServiceInstanceSelector() + serviceInstances := []registry.ServiceInstance{ + ®istry.DefaultServiceInstance{ + Id: "1", + ServiceName: "test1", + Host: "127.0.0.1:80", + Port: 0, + Enable: false, + Healthy: false, + Metadata: nil, + }, + ®istry.DefaultServiceInstance{ + Id: "2", + ServiceName: "test2", + Host: "127.0.0.1:80", + Port: 0, + Enable: false, + Healthy: false, + Metadata: nil, + }, + } + assert.NotNil(t, selector.Select(common.URL{}, serviceInstances)) +} diff --git a/metadata/report.go b/registry/servicediscovery/instance/service_instance_selector.go similarity index 54% rename from metadata/report.go rename to registry/servicediscovery/instance/service_instance_selector.go index f2380f50cd0eb15182c137f02e5f78b4ba8e4fd2..82fb3458be2838e9a5780e95be71aa89039b664f 100644 --- a/metadata/report.go +++ b/registry/servicediscovery/instance/service_instance_selector.go @@ -15,21 +15,14 @@ * limitations under the License. */ -package metadata +package instance import ( "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/metadata/definition" - "github.com/apache/dubbo-go/metadata/identifier" + "github.com/apache/dubbo-go/registry" ) -type MetadataReport interface { - StoreProviderMetadata(*identifier.MetadataIdentifier, *definition.ServiceDefinition) - StoreConsumerMetadata(*identifier.MetadataIdentifier, map[string]string) - SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, *common.URL) - RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) - GetExportedURLs(*identifier.ServiceMetadataIdentifier) []string - SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, []*common.URL) - GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) []string - GetServiceDefinition(*identifier.MetadataIdentifier) +type ServiceInstanceSelector interface { + //Select an instance of ServiceInstance by the specified ServiceInstance service instances + Select(url common.URL, serviceInstances []registry.ServiceInstance) registry.ServiceInstance } diff --git a/registry/servicediscovery/service_discovery_registry.go b/registry/servicediscovery/service_discovery_registry.go new file mode 100644 index 0000000000000000000000000000000000000000..061d832b0328a5e1754c7804bf40cf83ac216a8b --- /dev/null +++ b/registry/servicediscovery/service_discovery_registry.go @@ -0,0 +1,713 @@ +/* + * 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 servicediscovery + +import ( + "bytes" + "encoding/json" + "strconv" + "strings" + "sync" +) + +import ( + cm "github.com/Workiva/go-datastructures/common" + gxset "github.com/dubbogo/gost/container/set" + gxnet "github.com/dubbogo/gost/net" + perrors "github.com/pkg/errors" + "go.uber.org/atomic" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/mapping" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/metadata/service/exporter/configurable" + "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/registry/event" + "github.com/apache/dubbo-go/registry/servicediscovery/synthesizer" + "github.com/apache/dubbo-go/remoting" +) + +const ( + protocolName = "service-discovery" +) + +func init() { + extension.SetRegistry(protocolName, newServiceDiscoveryRegistry) +} + +// serviceDiscoveryRegistry is the implementation of application-level registry. +// It's completely different from other registry implementations +// This implementation is based on ServiceDiscovery abstraction and ServiceNameMapping +// In order to keep compatible with interface-level registry锛� +// this implementation is +type serviceDiscoveryRegistry struct { + lock sync.RWMutex + url *common.URL + serviceDiscovery registry.ServiceDiscovery + subscribedServices *gxset.HashSet + serviceNameMapping mapping.ServiceNameMapping + metaDataService service.MetadataService + registeredListeners *gxset.HashSet + subscribedURLsSynthesizers []synthesizer.SubscribedURLsSynthesizer + serviceRevisionExportedURLsCache map[string]map[string][]common.URL +} + +func newServiceDiscoveryRegistry(url *common.URL) (registry.Registry, error) { + + tryInitMetadataService() + + serviceDiscovery, err := creatServiceDiscovery(url) + if err != nil { + return nil, err + } + subscribedServices := parseServices(url.GetParam(constant.SUBSCRIBED_SERVICE_NAMES_KEY, "")) + subscribedURLsSynthesizers := synthesizer.GetAllSynthesizer() + serviceNameMapping := extension.GetGlobalServiceNameMapping() + metaDataService, err := extension.GetMetadataService(config.GetApplicationConfig().MetadataType) + if err != nil { + return nil, perrors.WithMessage(err, "could not init metadata service") + } + return &serviceDiscoveryRegistry{ + url: url, + serviceDiscovery: serviceDiscovery, + subscribedServices: subscribedServices, + subscribedURLsSynthesizers: subscribedURLsSynthesizers, + registeredListeners: gxset.NewSet(), + serviceRevisionExportedURLsCache: make(map[string]map[string][]common.URL, 8), + serviceNameMapping: serviceNameMapping, + metaDataService: metaDataService, + }, nil +} + +func (s *serviceDiscoveryRegistry) UnRegister(url common.URL) error { + if !shouldRegister(url) { + return nil + } + return s.metaDataService.UnexportURL(url) +} + +func (s *serviceDiscoveryRegistry) UnSubscribe(url *common.URL, listener registry.NotifyListener) error { + if !shouldSubscribe(*url) { + return nil + } + return s.metaDataService.UnsubscribeURL(*url) +} + +func creatServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) { + sdcName := url.GetParam(constant.SERVICE_DISCOVERY_KEY, "") + sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(sdcName) + if !ok { + return nil, perrors.Errorf("The service discovery with name: %s is not found", sdcName) + } + originServiceDiscovery, err := extension.GetServiceDiscovery(sdc.Protocol, sdcName) + if err != nil { + return nil, perrors.WithMessage(err, "Create service discovery fialed") + } + return event.NewEventPublishingServiceDiscovery(originServiceDiscovery), nil +} + +func parseServices(literalServices string) *gxset.HashSet { + set := gxset.NewSet() + if len(literalServices) == 0 { + return set + } + var splitServices = strings.Split(literalServices, ",") + for _, s := range splitServices { + if len(s) != 0 { + set.Add(s) + } + } + return set +} + +func (s *serviceDiscoveryRegistry) GetServiceDiscovery() registry.ServiceDiscovery { + return s.serviceDiscovery +} + +func (s *serviceDiscoveryRegistry) GetUrl() common.URL { + return *s.url +} + +func (s *serviceDiscoveryRegistry) IsAvailable() bool { + // TODO(whether available depends on metadata service and service discovery) + return true +} + +func (s *serviceDiscoveryRegistry) Destroy() { + err := s.serviceDiscovery.Destroy() + if err != nil { + logger.Errorf("destroy serviceDiscovery catch error:%s", err.Error()) + } +} + +func (s *serviceDiscoveryRegistry) Register(url common.URL) error { + if !shouldRegister(url) { + return nil + } + ok, err := s.metaDataService.ExportURL(url) + + if err != nil { + logger.Errorf("The URL[%s] registry catch error:%s!", url.String(), err.Error()) + return err + } + if !ok { + logger.Warnf("The URL[%s] has been registry!", url.String()) + } + + // we try to register this instance. Dubbo do this in org.apache.dubbo.config.bootstrap.DubboBootstrap + // But we don't want to design a similar bootstrap class. + ins, err := createInstance(url) + if err != nil { + return perrors.WithMessage(err, "could not create servcie instance, please check your service url") + } + + err = s.serviceDiscovery.Register(ins) + if err != nil { + return perrors.WithMessage(err, "register the service failed") + } + + err = s.metaDataService.PublishServiceDefinition(url) + if err != nil { + return perrors.WithMessage(err, "publish the service definition failed. ") + } + return s.serviceNameMapping.Map(url.GetParam(constant.INTERFACE_KEY, ""), + url.GetParam(constant.GROUP_KEY, ""), + url.GetParam(constant.Version, ""), + url.Protocol) +} + +func createInstance(url common.URL) (registry.ServiceInstance, error) { + appConfig := config.GetApplicationConfig() + port, err := strconv.ParseInt(url.Port, 10, 32) + if err != nil { + return nil, perrors.WithMessage(err, "invalid port: "+url.Port) + } + + host := url.Ip + if len(host) == 0 { + host, err = gxnet.GetLocalIP() + if err != nil { + return nil, perrors.WithMessage(err, "could not get the local Ip") + } + } + + // usually we will add more metadata + metadata := make(map[string]string, 8) + metadata[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME] = appConfig.MetadataType + + return ®istry.DefaultServiceInstance{ + ServiceName: appConfig.Name, + Host: host, + Port: int(port), + Id: host + constant.KEY_SEPARATOR + url.Port, + Enable: true, + Healthy: true, + Metadata: metadata, + }, nil +} + +func shouldRegister(url common.URL) bool { + side := url.GetParam(constant.SIDE_KEY, "") + if side == constant.PROVIDER_PROTOCOL { + return true + } + logger.Debugf("The URL should not be register.", url.String()) + return false +} + +func (s *serviceDiscoveryRegistry) Subscribe(url *common.URL, notify registry.NotifyListener) error { + if !shouldSubscribe(*url) { + return nil + } + _, err := s.metaDataService.SubscribeURL(*url) + if err != nil { + return perrors.WithMessage(err, "subscribe url error: "+url.String()) + } + services := s.getServices(*url) + if services.Empty() { + return perrors.Errorf("Should has at least one way to know which services this interface belongs to, "+ + "subscription url:%s", url.String()) + } + for _, srv := range services.Values() { + serviceName := srv.(string) + serviceInstances := s.serviceDiscovery.GetInstances(serviceName) + s.subscribe(url, notify, serviceName, serviceInstances) + listener := ®istry.ServiceInstancesChangedListener{ + ServiceName: serviceName, + ChangedNotify: &InstanceChangeNotify{ + notify: notify, + serviceDiscoveryRegistry: s, + }, + } + s.registerServiceInstancesChangedListener(*url, listener) + } + return nil +} + +func (s *serviceDiscoveryRegistry) registerServiceInstancesChangedListener(url common.URL, listener *registry.ServiceInstancesChangedListener) { + listenerId := listener.ServiceName + ":" + getUrlKey(url) + if !s.subscribedServices.Contains(listenerId) { + err := s.serviceDiscovery.AddListener(listener) + if err != nil { + logger.Errorf("add listener[%s] catch error,url:%s err:%s", listenerId, url.String(), err.Error()) + } + } + +} + +func getUrlKey(url common.URL) string { + var bf bytes.Buffer + if len(url.Protocol) != 0 { + bf.WriteString(url.Protocol) + bf.WriteString("://") + } + if len(url.Location) != 0 { + bf.WriteString(url.Location) + bf.WriteString(":") + bf.WriteString(url.Port) + } + if len(url.Path) != 0 { + bf.WriteString("/") + bf.WriteString(url.Path) + } + bf.WriteString("?") + appendParam(bf, constant.VERSION_KEY, url) + appendParam(bf, constant.GROUP_KEY, url) + appendParam(bf, constant.NACOS_PROTOCOL_KEY, url) + return bf.String() +} + +func appendParam(buffer bytes.Buffer, paramKey string, url common.URL) { + buffer.WriteString(paramKey) + buffer.WriteString("=") + buffer.WriteString(url.GetParam(paramKey, "")) +} + +func (s *serviceDiscoveryRegistry) subscribe(url *common.URL, notify registry.NotifyListener, + serviceName string, serviceInstances []registry.ServiceInstance) { + if len(serviceInstances) == 0 { + logger.Warnf("here is no instance in service[name : %s]", serviceName) + return + } + var subscribedURLs []common.URL + subscribedURLs = append(subscribedURLs, s.getExportedUrls(*url, serviceInstances)...) + if len(subscribedURLs) == 0 { + subscribedURLs = s.synthesizeSubscribedURLs(url, serviceInstances) + } + for _, url := range subscribedURLs { + notify.Notify(®istry.ServiceEvent{ + Action: remoting.EventTypeAdd, + Service: url, + }) + } + +} + +func (s *serviceDiscoveryRegistry) synthesizeSubscribedURLs(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL { + var urls []common.URL + for _, syn := range s.subscribedURLsSynthesizers { + if syn.Support(subscribedURL) { + urls = append(urls, syn.Synthesize(subscribedURL, serviceInstances)...) + } + } + return urls +} + +func shouldSubscribe(url common.URL) bool { + return !shouldRegister(url) +} + +func (s *serviceDiscoveryRegistry) getServices(url common.URL) *gxset.HashSet { + services := gxset.NewSet() + serviceNames := url.GetParam(constant.PROVIDER_BY, "") + if len(serviceNames) > 0 { + services = parseServices(serviceNames) + } + if services.Empty() { + services = s.findMappedServices(url) + if services.Empty() { + return s.subscribedServices + } + } + return services +} + +func (s *serviceDiscoveryRegistry) findMappedServices(url common.URL) *gxset.HashSet { + serviceInterface := url.GetParam(constant.INTERFACE_KEY, url.Path) + group := url.GetParam(constant.GROUP_KEY, "") + version := url.GetParam(constant.VERSION_KEY, "") + protocol := url.Protocol + serviceNames, err := s.serviceNameMapping.Get(serviceInterface, group, version, protocol) + if err != nil { + logger.Errorf("get serviceInterface:[%s] group:[%s] version:[%s] protocol:[%s] from "+ + "serviceNameMap error:%s", err.Error()) + return gxset.NewSet() + } + return serviceNames +} + +func (s *serviceDiscoveryRegistry) getExportedUrls(subscribedURL common.URL, serviceInstances []registry.ServiceInstance) []common.URL { + var filterInstances []registry.ServiceInstance + for _, s := range serviceInstances { + if !s.IsEnable() || !s.IsHealthy() { + continue + } + metaData := s.GetMetadata() + _, ok1 := metaData[constant.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME] + _, ok2 := metaData[constant.METADATA_SERVICE_URLS_PROPERTY_NAME] + if !ok1 && !ok2 { + continue + } + filterInstances = append(filterInstances, s) + } + if len(filterInstances) == 0 { + return []common.URL{} + } + s.prepareServiceRevisionExportedURLs(filterInstances) + subscribedURLs := s.cloneExportedURLs(subscribedURL, filterInstances) + return subscribedURLs +} + +// comparator is defined as Comparator for skip list to compare the URL +type comparator common.URL + +// Compare is defined as Comparator for skip list to compare the URL +func (c comparator) Compare(comp cm.Comparator) int { + a := common.URL(c).String() + b := common.URL(comp.(comparator)).String() + switch { + case a > b: + return 1 + case a < b: + return -1 + default: + return 0 + } +} + +func (s *serviceDiscoveryRegistry) getExportedUrlsByInst(serviceInstance registry.ServiceInstance) []common.URL { + var urls []common.URL + metadataStorageType := getExportedStoreType(serviceInstance) + proxyFactory := extension.GetMetadataServiceProxyFactory(metadataStorageType) + if proxyFactory == nil { + return urls + } + metadataService := proxyFactory.GetProxy(serviceInstance) + if metadataService == nil { + return urls + } + result, err := metadataService.GetExportedURLs(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) + if err != nil { + logger.Errorf("get exported urls catch error:%s,instance:%+v", err.Error(), serviceInstance) + return urls + } + + ret := make([]common.URL, 0, len(result)) + for _, ui := range result { + + u, err := common.NewURL(ui.(string)) + + if err != nil { + logger.Errorf("could not parse the url string to URL structure: %s", ui.(string), err) + continue + } + ret = append(ret, u) + } + return ret +} + +func (s *serviceDiscoveryRegistry) prepareServiceRevisionExportedURLs(serviceInstances []registry.ServiceInstance) { + s.lock.Lock() + // 1. expunge stale + s.expungeStaleRevisionExportedURLs(serviceInstances) + // 2. Initialize + s.initRevisionExportedURLs(serviceInstances) + s.lock.Unlock() +} + +func (s *serviceDiscoveryRegistry) expungeStaleRevisionExportedURLs(serviceInstances []registry.ServiceInstance) { + serviceName := serviceInstances[0].GetServiceName() + revisionExportedURLsMap, exist := s.serviceRevisionExportedURLsCache[serviceName] + if !exist { + return + } + existRevision := gxset.NewSet() + for k := range revisionExportedURLsMap { + existRevision.Add(k) + } + currentRevision := gxset.NewSet() + for _, s := range serviceInstances { + rv := getExportedServicesRevision(s) + if len(rv) != 0 { + currentRevision.Add(rv) + } + } + // staleRevisions = existedRevisions(copy) - currentRevisions + staleRevision := gxset.NewSet(existRevision.Values()...) + staleRevision.Remove(currentRevision.Values()...) + // remove exported URLs if staled + for _, s := range staleRevision.Values() { + delete(revisionExportedURLsMap, s.(string)) + } +} + +func (s *serviceDiscoveryRegistry) initRevisionExportedURLs(serviceInstances []registry.ServiceInstance) { + // initialize the revision exported URLs that the selected service instance exported + s.initSelectedRevisionExportedURLs(serviceInstances) + // initialize the revision exported URLs that other service instances exported + for _, serviceInstance := range serviceInstances { + s.initRevisionExportedURLsByInst(serviceInstance) + } +} + +func (s *serviceDiscoveryRegistry) initSelectedRevisionExportedURLs(serviceInstances []registry.ServiceInstance) { + for range serviceInstances { + selectServiceInstance := s.selectServiceInstance(serviceInstances) + revisionExportedURLs := s.initRevisionExportedURLsByInst(selectServiceInstance) + if len(revisionExportedURLs) > 0 { + // If the result is valid,break + break + } + } +} + +func (s *serviceDiscoveryRegistry) selectServiceInstance(serviceInstances []registry.ServiceInstance) registry.ServiceInstance { + size := len(serviceInstances) + if size == 0 { + return nil + } + if size == 1 { + return serviceInstances[0] + } + selectorName := s.url.GetParam(constant.SERVICE_INSTANCE_SELECTOR, "random") + selector, err := extension.GetServiceInstanceSelector(selectorName) + if err != nil { + logger.Errorf("get service instance selector cathe error:%s", err.Error()) + return nil + } + return selector.Select(*s.url, serviceInstances) +} + +func (s *serviceDiscoveryRegistry) initRevisionExportedURLsByInst(serviceInstance registry.ServiceInstance) []common.URL { + if serviceInstance == nil { + return []common.URL{} + } + serviceName := serviceInstance.GetServiceName() + revision := getExportedServicesRevision(serviceInstance) + revisionExportedURLsMap := s.serviceRevisionExportedURLsCache[serviceName] + if revisionExportedURLsMap == nil { + revisionExportedURLsMap = make(map[string][]common.URL, 4) + s.serviceRevisionExportedURLsCache[serviceName] = revisionExportedURLsMap + } + revisionExportedURLs := revisionExportedURLsMap[revision] + firstGet := false + if revisionExportedURLs == nil || len(revisionExportedURLs) == 0 { + if len(revisionExportedURLsMap) > 0 { + // The case is that current ServiceInstance with the different revision + logger.Warnf("The ServiceInstance[id: %s, host : %s , port : %s] has different revision : %s"+ + ", please make sure the service [name : %s] is changing or not.", serviceInstance.GetId(), + serviceInstance.GetHost(), serviceInstance.GetPort(), revision, serviceInstance.GetServiceName()) + } else { + firstGet = true + } + revisionExportedURLs = s.getExportedUrlsByInst(serviceInstance) + if revisionExportedURLs != nil { + revisionExportedURLsMap[revision] = revisionExportedURLs + logger.Debugf("Get the exported URLs[size : %s, first : %s] from the target service "+ + "instance [id: %s , service : %s , host : %s , port : %s , revision : %s]", + len(revisionExportedURLs), firstGet, serviceInstance.GetId(), serviceInstance.GetServiceName(), + serviceInstance.GetHost(), serviceInstance.GetPort(), revision) + } + } else { + // Else, The cache is hit + logger.Debugf("Get the exported URLs[size : %s] from cache, the instance"+ + "[id: %s , service : %s , host : %s , port : %s , revision : %s]", len(revisionExportedURLs), firstGet, + serviceInstance.GetId(), serviceInstance.GetServiceName(), serviceInstance.GetHost(), + serviceInstance.GetPort(), revision) + } + return revisionExportedURLs +} + +func getExportedServicesRevision(serviceInstance registry.ServiceInstance) string { + metaData := serviceInstance.GetMetadata() + return metaData[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME] +} + +func getExportedStoreType(serviceInstance registry.ServiceInstance) string { + metaData := serviceInstance.GetMetadata() + result, ok := metaData[constant.METADATA_STORAGE_TYPE_PROPERTY_NAME] + if !ok { + return constant.DEFAULT_METADATA_STORAGE_TYPE + } + return result +} + +func (s *serviceDiscoveryRegistry) cloneExportedURLs(url common.URL, serviceInsances []registry.ServiceInstance) []common.URL { + if len(serviceInsances) == 0 { + return []common.URL{} + } + var clonedExportedURLs []common.URL + removeParamSet := gxset.NewSet() + removeParamSet.Add(constant.PID_KEY) + removeParamSet.Add(constant.TIMESTAMP_KEY) + for _, serviceInstance := range serviceInsances { + templateExportURLs := s.getTemplateExportedURLs(url, serviceInstance) + host := serviceInstance.GetHost() + for _, u := range templateExportURLs { + port := strconv.Itoa(getProtocolPort(serviceInstance, u.Protocol)) + if u.Location != host || u.Port != port { + u.Port = port // reset port + u.Location = host + ":" + port // reset host + } + + cloneUrl := u.CloneExceptParams(removeParamSet) + clonedExportedURLs = append(clonedExportedURLs, *cloneUrl) + } + } + return clonedExportedURLs + +} + +type endpoint struct { + Port int `json:"port, omitempty"` + Protocol string `json:"protocol, omitempty"` +} + +func getProtocolPort(serviceInstance registry.ServiceInstance, protocol string) int { + md := serviceInstance.GetMetadata() + rawEndpoints := md[constant.SERVICE_INSTANCE_ENDPOINTS] + if len(rawEndpoints) == 0 { + return -1 + } + var endpoints []endpoint + err := json.Unmarshal([]byte(rawEndpoints), &endpoints) + if err != nil { + logger.Errorf("json umarshal rawEndpoints[%s] catch error:%s", rawEndpoints, err.Error()) + return -1 + } + for _, e := range endpoints { + if e.Protocol == protocol { + return e.Port + } + } + return -1 +} +func (s *serviceDiscoveryRegistry) getTemplateExportedURLs(url common.URL, serviceInstance registry.ServiceInstance) []common.URL { + exportedURLs := s.getRevisionExportedURLs(serviceInstance) + if len(exportedURLs) == 0 { + return []common.URL{} + } + return filterSubscribedURLs(url, exportedURLs) +} + +func (s *serviceDiscoveryRegistry) getRevisionExportedURLs(serviceInstance registry.ServiceInstance) []common.URL { + if serviceInstance == nil { + return []common.URL{} + } + serviceName := serviceInstance.GetServiceName() + revision := getExportedServicesRevision(serviceInstance) + s.lock.RLock() + revisionExportedURLsMap, exist := s.serviceRevisionExportedURLsCache[serviceName] + if !exist { + return []common.URL{} + } + exportedURLs, exist := revisionExportedURLsMap[revision] + if !exist { + return []common.URL{} + } + s.lock.RUnlock() + // Get a copy from source in order to prevent the caller trying to change the cached data + cloneExportedURLs := make([]common.URL, len(exportedURLs)) + copy(cloneExportedURLs, exportedURLs) + return cloneExportedURLs +} + +func filterSubscribedURLs(subscribedURL common.URL, exportedURLs []common.URL) []common.URL { + var filterExportedURLs []common.URL + for _, url := range exportedURLs { + if url.GetParam(constant.INTERFACE_KEY, url.Path) != subscribedURL.GetParam(constant.INTERFACE_KEY, url.Path) { + break + } + if url.GetParam(constant.VERSION_KEY, "") != subscribedURL.GetParam(constant.VERSION_KEY, "") { + break + } + if url.GetParam(constant.GROUP_KEY, "") != subscribedURL.GetParam(constant.GROUP_KEY, "") { + break + } + if len(subscribedURL.Protocol) != 0 { + if subscribedURL.Protocol != url.Protocol { + break + } + } + filterExportedURLs = append(filterExportedURLs, url) + } + return filterExportedURLs +} + +type InstanceChangeNotify struct { + notify registry.NotifyListener + serviceDiscoveryRegistry *serviceDiscoveryRegistry +} + +func (icn *InstanceChangeNotify) Notify(event observer.Event) { + + if se, ok := event.(*registry.ServiceInstancesChangedEvent); ok { + sdr := icn.serviceDiscoveryRegistry + sdr.subscribe(sdr.url, icn.notify, se.ServiceName, se.Instances) + } +} + +var ( + exporting = &atomic.Bool{} +) + +// tryInitMetadataService will try to initialize metadata service +// TODO (move to somewhere) +func tryInitMetadataService() { + + ms, err := extension.GetMetadataService(config.GetApplicationConfig().MetadataType) + if err != nil { + logger.Errorf("could not init metadata service", err) + } + + if !config.IsProvider() || exporting.Load() { + return + } + + // In theory, we can use sync.Once + // But sync.Once is not reentrant. + // Now the invocation chain is createRegistry -> tryInitMetadataService -> metadataServiceExporter.export + // -> createRegistry -> initMetadataService... + // So using sync.Once will result in dead lock + exporting.Store(true) + + expt := configurable.NewMetadataServiceExporter(ms) + + err = expt.Export() + if err != nil { + logger.Errorf("could not export the metadata service", err) + } + extension.GetGlobalDispatcher().Dispatch(event.NewServiceConfigExportedEvent(expt.(*configurable.MetadataServiceExporter).ServiceConfig)) +} diff --git a/registry/servicediscovery/service_discovery_registry_test.go b/registry/servicediscovery/service_discovery_registry_test.go new file mode 100644 index 0000000000000000000000000000000000000000..53eb86507e635be32eb362519922f7042f945519 --- /dev/null +++ b/registry/servicediscovery/service_discovery_registry_test.go @@ -0,0 +1,246 @@ +/* + * 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 servicediscovery + +import ( + "testing" +) + +import ( + "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/page" + "github.com/stretchr/testify/assert" +) +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/mapping" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/registry" +) + +var ( + serviceInterface = "org.apache.dubbo.metadata.MetadataService" + group = "dubbo-provider" + version = "1.0.0" +) + +func TestServiceDiscoveryRegistry_Register(t *testing.T) { + config.GetApplicationConfig().MetadataType = "mock" + extension.SetMetadataService("mock", func() (service service.MetadataService, err error) { + service = &mockMetadataService{} + return + }) + + extension.SetServiceDiscovery("mock", func(name string) (discovery registry.ServiceDiscovery, err error) { + return &mockServiceDiscovery{}, nil + }) + + extension.SetGlobalServiceNameMapping(func() mapping.ServiceNameMapping { + return &mockServiceNameMapping{} + }) + + extension.SetEventDispatcher("mock", func() observer.EventDispatcher { + return &mockEventDispatcher{} + }) + extension.SetAndInitGlobalDispatcher("mock") + + config.GetBaseConfig().ServiceDiscoveries["mock"] = &config.ServiceDiscoveryConfig{ + Protocol: "mock", + } + registryURL, _ := common.NewURL("service-discovery://localhost:12345", + common.WithParamsValue("service_discovery", "mock"), + common.WithParamsValue("subscribed-services", "a, b , c,d,e ,")) + url, _ := common.NewURL("dubbo://192.168.0.102:20880/" + serviceInterface + + "?&application=" + group + + "&interface=" + serviceInterface + + "&group=" + group + + "&version=" + version + + "&service_discovery=mock" + + "&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs" + + "&side=provider") + registry, err := newServiceDiscoveryRegistry(®istryURL) + assert.Nil(t, err) + assert.NotNil(t, registry) + registry.Register(url) +} + +type mockEventDispatcher struct { +} + +func (m *mockEventDispatcher) AddEventListener(listener observer.EventListener) { + +} + +func (m *mockEventDispatcher) AddEventListeners(listenersSlice []observer.EventListener) { + +} + +func (m *mockEventDispatcher) RemoveEventListener(listener observer.EventListener) { + panic("implement me") +} + +func (m *mockEventDispatcher) RemoveEventListeners(listenersSlice []observer.EventListener) { + panic("implement me") +} + +func (m *mockEventDispatcher) GetAllEventListeners() []observer.EventListener { + return []observer.EventListener{} +} + +func (m *mockEventDispatcher) RemoveAllEventListeners() { + panic("implement me") +} + +func (m *mockEventDispatcher) Dispatch(event observer.Event) { +} + +type mockServiceNameMapping struct { +} + +func (m *mockServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error { + return nil +} + +func (m *mockServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) { + panic("implement me") +} + +type mockServiceDiscovery struct { +} + +func (m *mockServiceDiscovery) String() string { + panic("implement me") +} + +func (m *mockServiceDiscovery) Destroy() error { + panic("implement me") +} + +func (m *mockServiceDiscovery) Register(instance registry.ServiceInstance) error { + return nil +} + +func (m *mockServiceDiscovery) Update(instance registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetDefaultPageSize() int { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetServices() *gxset.HashSet { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { + panic("implement me") +} + +func (m *mockServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + panic("implement me") +} + +func (m *mockServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + panic("implement me") +} + +type mockMetadataService struct { +} + +func (m *mockMetadataService) Reference() string { + panic("implement me") +} + +func (m *mockMetadataService) ServiceName() (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) { + return true, nil +} + +func (m *mockMetadataService) UnexportURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) UnsubscribeURL(url common.URL) error { + panic("implement me") +} + +func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error { + return nil +} + +func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { + panic("implement me") +} + +func (m *mockMetadataService) MethodMapper() map[string]string { + panic("implement me") +} + +func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { + panic("implement me") +} + +func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { + panic("implement me") +} + +func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { + panic("implement me") +} + +func (m *mockMetadataService) Version() (string, error) { + panic("implement me") +} diff --git a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go new file mode 100644 index 0000000000000000000000000000000000000000..086a26de58f8472e35e07a8a174fdee86afa82f2 --- /dev/null +++ b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.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 rest + +import ( + "net/url" + "strings" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/registry/servicediscovery/synthesizer" +) + +func init() { + synthesizer.AddSynthesizer(NewRestSubscribedURLsSynthesizer()) +} + +//SubscribedURLsSynthesizer implementation for rest protocol +type RestSubscribedURLsSynthesizer struct { +} + +func (r RestSubscribedURLsSynthesizer) Support(subscribedURL *common.URL) bool { + if "rest" == subscribedURL.Protocol { + return true + } + return false +} + +func (r RestSubscribedURLsSynthesizer) Synthesize(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL { + urls := make([]common.URL, len(serviceInstances), len(serviceInstances)) + for i, s := range serviceInstances { + splitHost := strings.Split(s.GetHost(), ":") + u := common.NewURLWithOptions(common.WithProtocol(subscribedURL.Protocol), common.WithIp(splitHost[0]), + common.WithPort(splitHost[1]), common.WithPath(subscribedURL.GetParam(constant.INTERFACE_KEY, subscribedURL.Path)), + common.WithParams(url.Values{}), + common.WithParamsValue(constant.SIDE_KEY, constant.PROVIDER_PROTOCOL), + common.WithParamsValue(constant.APPLICATION_KEY, s.GetServiceName()), + common.WithParamsValue(constant.REGISTRY_KEY, "true"), + ) + urls[i] = *u + } + return urls +} + +func NewRestSubscribedURLsSynthesizer() RestSubscribedURLsSynthesizer { + return RestSubscribedURLsSynthesizer{} +} diff --git a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b52cc2323d6f9ae1bca8cfd1a4c5217af5e25f12 --- /dev/null +++ b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go @@ -0,0 +1,75 @@ +/* + * 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 rest + +import ( + "net/url" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/registry" +) + +func TestRestSubscribedURLsSynthesizer_Synthesize(t *testing.T) { + syn := RestSubscribedURLsSynthesizer{} + subUrl, _ := common.NewURL("rest://127.0.0.1:20000/org.apache.dubbo-go.mockService") + instances := []registry.ServiceInstance{ + ®istry.DefaultServiceInstance{ + Id: "test1", + ServiceName: "test1", + Host: "127.0.0.1:80", + Port: 80, + Enable: false, + Healthy: false, + Metadata: nil, + }, + ®istry.DefaultServiceInstance{ + Id: "test2", + ServiceName: "test2", + Host: "127.0.0.2:8081", + Port: 8081, + Enable: false, + Healthy: false, + Metadata: nil, + }, + } + + var expectUrls []common.URL + u1 := common.NewURLWithOptions(common.WithProtocol("rest"), common.WithIp("127.0.0.1"), + common.WithPort("80"), common.WithPath("org.apache.dubbo-go.mockService"), + common.WithParams(url.Values{}), + common.WithParamsValue(constant.SIDE_KEY, constant.PROVIDER_PROTOCOL), + common.WithParamsValue(constant.APPLICATION_KEY, "test1"), + common.WithParamsValue(constant.REGISTRY_KEY, "true")) + u2 := common.NewURLWithOptions(common.WithProtocol("rest"), common.WithIp("127.0.0.2"), + common.WithPort("8081"), common.WithPath("org.apache.dubbo-go.mockService"), + common.WithParams(url.Values{}), + common.WithParamsValue(constant.SIDE_KEY, constant.PROVIDER_PROTOCOL), + common.WithParamsValue(constant.APPLICATION_KEY, "test2"), + common.WithParamsValue(constant.REGISTRY_KEY, "true")) + expectUrls = append(expectUrls, *u1, *u2) + result := syn.Synthesize(&subUrl, instances) + assert.Equal(t, expectUrls, result) +} diff --git a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go new file mode 100644 index 0000000000000000000000000000000000000000..415ca35fbad2fa335d687dc7a7718fa3a4b2b487 --- /dev/null +++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.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 synthesizer + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/registry" +) + +// SubscribedURLsSynthesizer is used to synthesize the subscribed url +type SubscribedURLsSynthesizer interface { + // Supports the synthesis of the subscribed url or not + Support(subscribedURL *common.URL) bool + // synthesize the subscribed url + Synthesize(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL +} diff --git a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go new file mode 100644 index 0000000000000000000000000000000000000000..c9b1449bef1a8fba0afb8cda163d740e34ac1157 --- /dev/null +++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_factory.go @@ -0,0 +1,41 @@ +/* + * 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 synthesizer + +import ( + "sync" +) + +var ( + synthesizers []SubscribedURLsSynthesizer + synthesizerMutex sync.RWMutex +) + +// nolint +func AddSynthesizer(synthesizer SubscribedURLsSynthesizer) { + synthesizerMutex.Lock() + defer synthesizerMutex.Unlock() + synthesizers = append(synthesizers, synthesizer) +} + +// nolint +func GetAllSynthesizer() []SubscribedURLsSynthesizer { + synthesizerMutex.RLock() + defer synthesizerMutex.RUnlock() + return synthesizers +} diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index c5b2f33c6107e82aa172c818c0d8aca1483248c6..ec82fa0309118fba4b5c21772d4dfd356f3b0c5c 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -37,7 +37,7 @@ import ( // RegistryDataListener contains all URL information subscribed by zookeeper registry type RegistryDataListener struct { - subscribed map[*common.URL]config_center.ConfigurationListener + subscribed map[string]config_center.ConfigurationListener mutex sync.Mutex closed bool } @@ -45,7 +45,7 @@ type RegistryDataListener struct { // NewRegistryDataListener constructs a new RegistryDataListener func NewRegistryDataListener() *RegistryDataListener { return &RegistryDataListener{ - subscribed: make(map[*common.URL]config_center.ConfigurationListener)} + subscribed: make(map[string]config_center.ConfigurationListener)} } // SubscribeURL is used to set a watch listener for url @@ -53,7 +53,17 @@ func (l *RegistryDataListener) SubscribeURL(url *common.URL, listener config_cen if l.closed { return } - l.subscribed[url] = listener + l.subscribed[url.ServiceKey()] = listener +} + +// UnSubscribeURL is used to set a watch listener for url +func (l *RegistryDataListener) UnSubscribeURL(url *common.URL) config_center.ConfigurationListener { + if l.closed { + return nil + } + listener := l.subscribed[url.ServiceKey()] + delete(l.subscribed, url.ServiceKey()) + return listener } // DataChange accepts all events sent from the zookeeper server and trigger the corresponding listener for processing @@ -75,8 +85,8 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { if l.closed { return false } - for url, listener := range l.subscribed { - if serviceURL.URLEqual(*url) { + for serviceKey, listener := range l.subscribed { + if serviceURL.ServiceKey() == serviceKey { listener.Process( &config_center.ConfigChangeEvent{ Key: eventType.Path, @@ -101,18 +111,25 @@ func (l *RegistryDataListener) Close() { // RegistryConfigurationListener represent the processor of zookeeper watcher type RegistryConfigurationListener struct { - client *zk.ZookeeperClient - registry *zkRegistry - events chan *config_center.ConfigChangeEvent - isClosed bool - close chan struct{} - closeOnce sync.Once + client *zk.ZookeeperClient + registry *zkRegistry + events chan *config_center.ConfigChangeEvent + isClosed bool + close chan struct{} + closeOnce sync.Once + subscribeURL *common.URL } // NewRegistryConfigurationListener for listening the event of zk. -func NewRegistryConfigurationListener(client *zk.ZookeeperClient, reg *zkRegistry) *RegistryConfigurationListener { +func NewRegistryConfigurationListener(client *zk.ZookeeperClient, reg *zkRegistry, conf *common.URL) *RegistryConfigurationListener { reg.WaitGroup().Add(1) - return &RegistryConfigurationListener{client: client, registry: reg, events: make(chan *config_center.ConfigChangeEvent, 32), isClosed: false, close: make(chan struct{}, 1)} + return &RegistryConfigurationListener{ + client: client, + registry: reg, + events: make(chan *config_center.ConfigChangeEvent, 32), + isClosed: false, + close: make(chan struct{}, 1), + subscribeURL: conf} } // Process submit the ConfigChangeEvent to the event chan to notify all observer diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go index 1e7bd08adef5ac9920413fd198b726f49c11ecd4..8f2ac1023b8ad34938b9996b480e3bbc4adbaaea 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -20,6 +20,7 @@ package zookeeper import ( "fmt" "net/url" + "path" "sync" "time" ) @@ -129,12 +130,17 @@ func (r *zkRegistry) InitListeners() { recoverd := r.dataListener.subscribed if recoverd != nil && len(recoverd) > 0 { // recover all subscribed url - for conf, oldListener := range recoverd { - if regConfigListener, ok := oldListener.(*RegistryConfigurationListener); ok { + for _, oldListener := range recoverd { + var ( + regConfigListener *RegistryConfigurationListener + ok bool + ) + + if regConfigListener, ok = oldListener.(*RegistryConfigurationListener); ok { regConfigListener.Close() } - newDataListener.SubscribeURL(conf, NewRegistryConfigurationListener(r.client, r)) - go r.listener.ListenServiceEvent(conf, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), newDataListener) + newDataListener.SubscribeURL(regConfigListener.subscribeURL, NewRegistryConfigurationListener(r.client, r, regConfigListener.subscribeURL)) + go r.listener.ListenServiceEvent(regConfigListener.subscribeURL, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(regConfigListener.subscribeURL.Service())), newDataListener) } } @@ -152,11 +158,24 @@ func (r *zkRegistry) DoRegister(root string, node string) error { return r.registerTempZookeeperNode(root, node) } +func (r *zkRegistry) DoUnregister(root string, node string) error { + r.cltLock.Lock() + defer r.cltLock.Unlock() + if !r.ZkClient().ZkConnValid() { + return perrors.Errorf("zk client is not valid.") + } + return r.ZkClient().Delete(path.Join(root, node)) +} + // DoSubscribe actually subscribes the provider URL func (r *zkRegistry) DoSubscribe(conf *common.URL) (registry.Listener, error) { return r.getListener(conf) } +func (r *zkRegistry) DoUnsubscribe(conf *common.URL) (registry.Listener, error) { + return r.getCloseListener(conf) +} + // CloseAndNilClient closes listeners and clear client func (r *zkRegistry) CloseAndNilClient() { r.client.Close() @@ -226,9 +245,9 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen dataListener := r.dataListener dataListener.mutex.Lock() defer dataListener.mutex.Unlock() - if r.dataListener.subscribed[conf] != nil { + if r.dataListener.subscribed[conf.ServiceKey()] != nil { - zkListener, _ := r.dataListener.subscribed[conf].(*RegistryConfigurationListener) + zkListener, _ := r.dataListener.subscribed[conf.ServiceKey()].(*RegistryConfigurationListener) if zkListener != nil { r.listenerLock.Lock() defer r.listenerLock.Unlock() @@ -240,7 +259,7 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen } } - zkListener = NewRegistryConfigurationListener(r.client, r) + zkListener = NewRegistryConfigurationListener(r.client, r, conf) if r.listener == nil { r.cltLock.Lock() client := r.client @@ -264,3 +283,37 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen return zkListener, nil } + +func (r *zkRegistry) getCloseListener(conf *common.URL) (*RegistryConfigurationListener, error) { + + var zkListener *RegistryConfigurationListener + r.dataListener.mutex.Lock() + configurationListener := r.dataListener.subscribed[conf.ServiceKey()] + if configurationListener != nil { + + zkListener, _ := configurationListener.(*RegistryConfigurationListener) + if zkListener != nil { + if zkListener.isClosed { + return nil, perrors.New("configListener already been closed") + } + } + } + + zkListener = r.dataListener.UnSubscribeURL(conf).(*RegistryConfigurationListener) + r.dataListener.mutex.Unlock() + + if r.listener == nil { + return nil, perrors.New("listener is null can not close.") + } + + //Interested register to dataconfig. + r.listenerLock.Lock() + listener := r.listener + r.listener = nil + r.listenerLock.Unlock() + + r.dataListener.Close() + listener.Close() + + return zkListener, nil +} diff --git a/registry/zookeeper/registry_test.go b/registry/zookeeper/registry_test.go index 688deccfbec67771c4071f6307802a16e4e0fc8b..d915fc2ce10359f0dd1970daf019746ce066f511 100644 --- a/registry/zookeeper/registry_test.go +++ b/registry/zookeeper/registry_test.go @@ -45,6 +45,31 @@ func Test_Register(t *testing.T) { assert.NoError(t, err) } +func Test_UnRegister(t *testing.T) { + // register + regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithParamsValue("serviceid", "soa.mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + + ts, reg, _ := newMockZkRegistry(®url) + defer ts.Stop() + err := reg.Register(url) + children, _ := reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") + assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26cluster%3Dmock%26.*.serviceid%3Dsoa.mock", children) + assert.NoError(t, err) + + err = reg.UnRegister(url) + children, err = reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") + assert.Equal(t, 0, len(children)) + assert.Error(t, err) + assert.True(t, reg.IsAvailable()) + + err = reg.Register(url) + children, _ = reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") + assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26cluster%3Dmock%26.*.serviceid%3Dsoa.mock", children) + assert.NoError(t, err) + +} + func Test_Subscribe(t *testing.T) { regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) @@ -74,6 +99,39 @@ func Test_Subscribe(t *testing.T) { defer ts.Stop() } +func Test_UnSubscribe(t *testing.T) { + regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + ts, reg, _ := newMockZkRegistry(®url) + + //provider register + err := reg.Register(url) + assert.NoError(t, err) + + if err != nil { + return + } + + //consumer register + regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) + _, reg2, _ := newMockZkRegistry(®url, zookeeper.WithTestCluster(ts)) + + reg2.Register(url) + listener, _ := reg2.DoSubscribe(&url) + + serviceEvent, _ := listener.Next() + assert.NoError(t, err) + if err != nil { + return + } + assert.Regexp(t, ".*ServiceEvent{Action{add}.*", serviceEvent.String()) + + reg2.UnSubscribe(&url, nil) + assert.Nil(t, reg2.listener) + + defer ts.Stop() +} + func Test_ConsumerDestory(t *testing.T) { regurl, _ := common.NewURL("registry://127.0.0.1:1111", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))) url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) diff --git a/registry/zookeeper/service_discovery.go b/registry/zookeeper/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..5ad83ef90947afc0a5ca75af5009e8b55b4f6627 --- /dev/null +++ b/registry/zookeeper/service_discovery.go @@ -0,0 +1,351 @@ +/* + * 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 zookeeper + +import ( + "fmt" + "net/url" + "strconv" + "strings" + "sync" +) + +import ( + "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/page" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/zookeeper" + "github.com/apache/dubbo-go/remoting/zookeeper/curator_discovery" +) + +const ( + // RegistryZkClient zk client name + ServiceDiscoveryZkClient = "zk service discovery" +) + +var ( + // 16 would be enough. We won't use concurrentMap because in most cases, there are not race condition + instanceMap = make(map[string]registry.ServiceDiscovery, 16) + initLock sync.Mutex +) + +// init will put the service discovery into extension +func init() { + extension.SetServiceDiscovery(constant.ZOOKEEPER_KEY, newZookeeperServiceDiscovery) +} + +type zookeeperServiceDiscovery struct { + client *zookeeper.ZookeeperClient + csd *curator_discovery.ServiceDiscovery + listener *zookeeper.ZkEventListener + url *common.URL + wg sync.WaitGroup + cltLock sync.Mutex + listenLock sync.Mutex + done chan struct{} + rootPath string + listenNames []string +} + +// newZookeeperServiceDiscovery the constructor of newZookeeperServiceDiscovery +func newZookeeperServiceDiscovery(name string) (registry.ServiceDiscovery, error) { + instance, ok := instanceMap[name] + if ok { + return instance, nil + } + + initLock.Lock() + defer initLock.Unlock() + + // double check + instance, ok = instanceMap[name] + if ok { + return instance, nil + } + + sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name) + if !ok || len(sdc.RemoteRef) == 0 { + return nil, perrors.New("could not init the instance because the config is invalid") + } + remoteConfig, ok := config.GetBaseConfig().GetRemoteConfig(sdc.RemoteRef) + if !ok { + return nil, perrors.New("could not find the remote config for name: " + sdc.RemoteRef) + } + rootPath := remoteConfig.GetParam("rootPath", "/services") + url := common.NewURLWithOptions( + common.WithParams(make(url.Values)), + common.WithPassword(remoteConfig.Password), + common.WithUsername(remoteConfig.Username), + common.WithParamsValue(constant.REGISTRY_TIMEOUT_KEY, remoteConfig.TimeoutStr)) + url.Location = remoteConfig.Address + zksd := &zookeeperServiceDiscovery{ + url: url, + rootPath: rootPath, + } + err := zookeeper.ValidateZookeeperClient(zksd, zookeeper.WithZkName(ServiceDiscoveryZkClient)) + if err != nil { + return nil, err + } + go zookeeper.HandleClientRestart(zksd) + zksd.csd = curator_discovery.NewServiceDiscovery(zksd.client, rootPath) + return zksd, nil +} + +// nolint +func (zksd *zookeeperServiceDiscovery) ZkClient() *zookeeper.ZookeeperClient { + return zksd.client +} + +// nolint +func (zksd *zookeeperServiceDiscovery) SetZkClient(client *zookeeper.ZookeeperClient) { + zksd.client = client +} + +// nolint +func (zksd *zookeeperServiceDiscovery) ZkClientLock() *sync.Mutex { + return &zksd.cltLock +} + +// nolint +func (zksd *zookeeperServiceDiscovery) WaitGroup() *sync.WaitGroup { + return &zksd.wg +} + +// nolint +func (zksd *zookeeperServiceDiscovery) Done() chan struct{} { + return zksd.done +} + +// RestartCallBack when zookeeper connection reconnect this function will be invoked. +// try to re-register service, and listen services +func (zksd *zookeeperServiceDiscovery) RestartCallBack() bool { + zksd.csd.ReRegisterServices() + zksd.listenLock.Lock() + defer zksd.listenLock.Unlock() + for _, name := range zksd.listenNames { + zksd.csd.ListenServiceEvent(name, zksd) + } + return true +} + +// nolint +func (zksd *zookeeperServiceDiscovery) GetUrl() common.URL { + return *zksd.url +} + +// nolint +func (zksd *zookeeperServiceDiscovery) String() string { + return fmt.Sprintf("zookeeper-service-discovery[%s]", zksd.url) +} + +// Close client be closed +func (zksd *zookeeperServiceDiscovery) Destroy() error { + zksd.client.Close() + return nil +} + +// Register will register service in zookeeper, instance convert to curator's service instance +// which define in curator-x-discovery. +func (zksd *zookeeperServiceDiscovery) Register(instance registry.ServiceInstance) error { + cris := zksd.toCuratorInstance(instance) + return zksd.csd.RegisterService(cris) +} + +// Register will update service in zookeeper, instance convert to curator's service instance +// which define in curator-x-discovery, please refer to https://github.com/apache/curator. +func (zksd *zookeeperServiceDiscovery) Update(instance registry.ServiceInstance) error { + cris := zksd.toCuratorInstance(instance) + return zksd.csd.UpdateService(cris) +} + +// Unregister will unregister the instance in zookeeper +func (zksd *zookeeperServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + cris := zksd.toCuratorInstance(instance) + return zksd.csd.UnregisterService(cris) +} + +// GetDefaultPageSize will return the constant registry.DefaultPageSize +func (zksd *zookeeperServiceDiscovery) GetDefaultPageSize() int { + return registry.DefaultPageSize +} + +// GetServices will return the all services in zookeeper +func (zksd *zookeeperServiceDiscovery) GetServices() *gxset.HashSet { + services, err := zksd.csd.QueryForNames() + res := gxset.NewSet() + if err != nil { + logger.Errorf("[zkServiceDiscovery] Could not query the services: %v", err) + return res + } + for _, service := range services { + res.Add(service) + } + return res +} + +// GetInstances will return the instances in a service +func (zksd *zookeeperServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + criss, err := zksd.csd.QueryForInstances(serviceName) + if err != nil { + logger.Errorf("[zkServiceDiscovery] Could not query the instances for service{%s}, error = err{%v} ", + serviceName, err) + return make([]registry.ServiceInstance, 0, 0) + } + iss := make([]registry.ServiceInstance, 0, len(criss)) + for _, cris := range criss { + iss = append(iss, zksd.toZookeeperInstance(cris)) + } + return iss +} + +// GetInstancesByPage will return the instances +func (zksd *zookeeperServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + all := zksd.GetInstances(serviceName) + res := make([]interface{}, 0, pageSize) + // could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance + for i := offset; i < len(all) && i < offset+pageSize; i++ { + res = append(res, all[i]) + } + return gxpage.New(offset, pageSize, res, len(all)) +} + +// GetHealthyInstancesByPage will return the instance +// In zookeeper, all service instance's is healthy. +// However, the healthy parameter in this method maybe false. So we can not use that API. +// Thus, we must query all instances and then do filter +func (zksd *zookeeperServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + all := zksd.GetInstances(serviceName) + res := make([]interface{}, 0, pageSize) + // could not use res = all[a:b] here because the res should be []interface{}, not []ServiceInstance + var ( + i = offset + count = 0 + ) + for i < len(all) && count < pageSize { + ins := all[i] + if ins.IsHealthy() == healthy { + res = append(res, all[i]) + count++ + } + i++ + } + return gxpage.New(offset, pageSize, res, len(all)) +} + +// GetRequestInstances will return the instances +func (zksd *zookeeperServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { + res := make(map[string]gxpage.Pager, len(serviceNames)) + for _, name := range serviceNames { + res[name] = zksd.GetInstancesByPage(name, offset, requestedSize) + } + return res +} + +// AddListener ListenServiceEvent will add a data listener in service +func (zksd *zookeeperServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + zksd.listenLock.Lock() + defer zksd.listenLock.Unlock() + zksd.listenNames = append(zksd.listenNames, listener.ServiceName) + zksd.csd.ListenServiceEvent(listener.ServiceName, zksd) + return nil +} + +func (zksd *zookeeperServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return zksd.DispatchEventForInstances(serviceName, zksd.GetInstances(serviceName)) +} + +// DispatchEventForInstances dispatch ServiceInstancesChangedEvent +func (zksd *zookeeperServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return zksd.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances)) +} + +// nolint +func (zksd *zookeeperServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + extension.GetGlobalDispatcher().Dispatch(event) + return nil +} + +// DataChange implement DataListener's DataChange function +// to resolve event to do DispatchEventByServiceName +func (zksd *zookeeperServiceDiscovery) DataChange(eventType remoting.Event) bool { + path := strings.TrimPrefix(eventType.Path, zksd.rootPath) + path = strings.TrimPrefix(path, constant.PATH_SEPARATOR) + // get service name in zk path + serviceName := strings.Split(path, constant.PATH_SEPARATOR)[0] + err := zksd.DispatchEventByServiceName(serviceName) + if err != nil { + logger.Errorf("[zkServiceDiscovery] DispatchEventByServiceName{%s} error = err{%v}", serviceName, err) + return false + } + return true +} + +// toCuratorInstance convert to curator's service instance +func (zksd *zookeeperServiceDiscovery) toCuratorInstance(instance registry.ServiceInstance) *curator_discovery.ServiceInstance { + id := instance.GetHost() + ":" + strconv.Itoa(instance.GetPort()) + pl := make(map[string]interface{}, 8) + pl["id"] = id + pl["name"] = instance.GetServiceName() + pl["metadata"] = instance.GetMetadata() + cuis := &curator_discovery.ServiceInstance{ + Name: instance.GetServiceName(), + Id: id, + Address: instance.GetHost(), + Port: instance.GetPort(), + Payload: pl, + RegistrationTimeUTC: 0, + } + return cuis +} + +// toZookeeperInstance convert to registry's service instance +func (zksd *zookeeperServiceDiscovery) toZookeeperInstance(cris *curator_discovery.ServiceInstance) registry.ServiceInstance { + pl, ok := cris.Payload.(map[string]interface{}) + if !ok { + logger.Errorf("[zkServiceDiscovery] toZookeeperInstance{%s} payload is not map[string]interface{}", cris.Id) + return nil + } + mdi, ok := pl["metadata"].(map[string]interface{}) + if !ok { + logger.Errorf("[zkServiceDiscovery] toZookeeperInstance{%s} metadata is not map[string]interface{}", cris.Id) + return nil + } + md := make(map[string]string, len(mdi)) + for k, v := range mdi { + md[k] = fmt.Sprint(v) + } + return ®istry.DefaultServiceInstance{ + Id: cris.Id, + ServiceName: cris.Name, + Host: cris.Address, + Port: cris.Port, + Enable: true, + Healthy: true, + Metadata: md, + } +} diff --git a/registry/zookeeper/service_discovery_test.go b/registry/zookeeper/service_discovery_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ea3c7ddd48adc0adc4162d8306d28283575f694a --- /dev/null +++ b/registry/zookeeper/service_discovery_test.go @@ -0,0 +1,197 @@ +/* + * 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 zookeeper + +import ( + "strconv" + "sync" + "testing" +) + +import ( + "github.com/dubbogo/go-zookeeper/zk" + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/registry" +) + +var testName = "test" + +func prepareData(t *testing.T) *zk.TestCluster { + ts, err := zk.StartTestCluster(1, nil, nil) + assert.NoError(t, err) + assert.NotNil(t, ts.Servers[0]) + address := "127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port) + + config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{ + Protocol: "zookeeper", + RemoteRef: "test", + } + + config.GetBaseConfig().Remotes[testName] = &config.RemoteConfig{ + Address: address, + TimeoutStr: "10s", + } + return ts +} + +func TestNewZookeeperServiceDiscovery(t *testing.T) { + name := "zookeeper1" + _, err := newZookeeperServiceDiscovery(name) + + // the ServiceDiscoveryConfig not found + assert.NotNil(t, err) + + sdc := &config.ServiceDiscoveryConfig{ + Protocol: "zookeeper", + RemoteRef: "mock", + } + config.GetBaseConfig().ServiceDiscoveries[name] = sdc + _, err = newZookeeperServiceDiscovery(name) + + // RemoteConfig not found + assert.NotNil(t, err) +} + +func TestCURDZookeeperServiceDiscovery(t *testing.T) { + ts := prepareData(t) + defer ts.Stop() + sd, err := newZookeeperServiceDiscovery(testName) + assert.Nil(t, err) + defer sd.Destroy() + md := make(map[string]string) + md["t1"] = "test1" + err = sd.Register(®istry.DefaultServiceInstance{ + Id: "testId", + ServiceName: testName, + Host: "127.0.0.1", + Port: 2233, + Enable: true, + Healthy: true, + Metadata: md, + }) + assert.Nil(t, err) + + testsPager := sd.GetHealthyInstancesByPage(testName, 0, 1, true) + assert.Equal(t, 1, testsPager.GetDataSize()) + assert.Equal(t, 1, testsPager.GetTotalPages()) + test := testsPager.GetData()[0].(registry.ServiceInstance) + assert.Equal(t, "127.0.0.1:2233", test.GetId()) + assert.Equal(t, "test1", test.GetMetadata()["t1"]) + + md["t1"] = "test12" + err = sd.Update(®istry.DefaultServiceInstance{ + Id: "testId", + ServiceName: testName, + Host: "127.0.0.1", + Port: 2233, + Enable: true, + Healthy: true, + Metadata: md, + }) + assert.Nil(t, err) + + testsPager = sd.GetInstancesByPage(testName, 0, 1) + assert.Equal(t, 1, testsPager.GetDataSize()) + test = testsPager.GetData()[0].(registry.ServiceInstance) + assert.Equal(t, "test12", test.GetMetadata()["t1"]) + + testsMap := sd.GetRequestInstances([]string{testName}, 0, 1) + assert.Equal(t, 1, len(testsMap)) + assert.Equal(t, 1, testsMap[testName].GetDataSize()) + test = testsMap[testName].GetData()[0].(registry.ServiceInstance) + assert.Equal(t, "test12", test.GetMetadata()["t1"]) + + names := sd.GetServices() + assert.Equal(t, 1, names.Size()) + assert.Equal(t, testName, names.Values()[0]) + + err = sd.Unregister(®istry.DefaultServiceInstance{ + Id: "testId", + ServiceName: testName, + Host: "127.0.0.1", + Port: 2233, + Enable: true, + Healthy: true, + Metadata: nil, + }) + assert.Nil(t, err) +} + +func TestAddListenerZookeeperServiceDiscovery(t *testing.T) { + ts := prepareData(t) + defer ts.Stop() + sd, err := newZookeeperServiceDiscovery(testName) + assert.Nil(t, err) + defer sd.Destroy() + + err = sd.Register(®istry.DefaultServiceInstance{ + Id: "testId", + ServiceName: testName, + Host: "127.0.0.1", + Port: 2233, + Enable: true, + Healthy: true, + Metadata: nil, + }) + assert.Nil(t, err) + + assert.Nil(t, err) + wg := &sync.WaitGroup{} + wg.Add(1) + tn := &testNotify{ + wg: wg, + t: t, + } + sicl := ®istry.ServiceInstancesChangedListener{ + ServiceName: testName, + ChangedNotify: tn, + } + extension.SetAndInitGlobalDispatcher("direct") + extension.GetGlobalDispatcher().AddEventListener(sicl) + err = sd.AddListener(sicl) + assert.Nil(t, err) + + err = sd.Update(®istry.DefaultServiceInstance{ + Id: "testId", + ServiceName: testName, + Host: "127.0.0.1", + Port: 2233, + Enable: true, + Healthy: true, + Metadata: nil, + }) + tn.wg.Wait() +} + +type testNotify struct { + wg *sync.WaitGroup + t *testing.T +} + +func (tn *testNotify) Notify(e observer.Event) { + ice := e.(*registry.ServiceInstancesChangedEvent) + assert.Equal(tn.t, 1, len(ice.Instances)) + assert.Equal(tn.t, "127.0.0.1:2233", ice.Instances[0].GetId()) + tn.wg.Done() +} diff --git a/remoting/consul/test_agent.go b/remoting/consul/test_agent.go new file mode 100644 index 0000000000000000000000000000000000000000..1744da7bd9992ae3cd376b22e9ea3a135dce2b16 --- /dev/null +++ b/remoting/consul/test_agent.go @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package consul + +import ( + "io/ioutil" + "os" + "strconv" + "testing" +) + +import ( + "github.com/hashicorp/consul/agent" +) + +// Consul agent, used for test, simulates +// an embedded consul server. +type ConsulAgent struct { + dataDir string + testAgent *agent.TestAgent +} + +func NewConsulAgent(t *testing.T, port int) *ConsulAgent { + dataDir, _ := ioutil.TempDir("./", "agent") + hcl := ` + ports { + http = ` + strconv.Itoa(port) + ` + } + data_dir = "` + dataDir + `" + ` + testAgent := &agent.TestAgent{Name: t.Name(), DataDir: dataDir, HCL: hcl} + testAgent.Start(t) + + consulAgent := &ConsulAgent{ + dataDir: dataDir, + testAgent: testAgent, + } + return consulAgent +} + +func (consulAgent *ConsulAgent) Close() error { + var err error + + err = consulAgent.testAgent.Shutdown() + if err != nil { + return err + } + return os.RemoveAll(consulAgent.dataDir) +} diff --git a/remoting/consul/test_agent_test.go b/remoting/consul/test_agent_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8cf0ac6cd80e517ab7bc1b52cc7774a708082d5e --- /dev/null +++ b/remoting/consul/test_agent_test.go @@ -0,0 +1,32 @@ +/* + * 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 consul + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestNewConsulAgent(t *testing.T) { + consulAgent := NewConsulAgent(t, 8500) + err := consulAgent.Close() + assert.NoError(t, err) +} diff --git a/remoting/etcdv3/client.go b/remoting/etcdv3/client.go index b337c79584cc5058e89bd582b007e72fb10da7ee..7632a7cd042d36db5e02280a14224b83e8736e6c 100644 --- a/remoting/etcdv3/client.go +++ b/remoting/etcdv3/client.go @@ -19,7 +19,6 @@ package etcdv3 import ( "context" - "path" "sync" "time" ) @@ -42,6 +41,8 @@ const ( MaxFailTimes = 15 // RegistryETCDV3Client client name RegistryETCDV3Client = "etcd registry" + // metadataETCDV3Client client name + MetadataETCDV3Client = "etcd metadata" ) var ( @@ -106,7 +107,7 @@ func ValidateClient(container clientFacade, opts ...Option) error { // new Client if container.Client() == nil { - newClient, err := newClient(options.name, options.endpoints, options.timeout, options.heartbeat) + newClient, err := NewClient(options.name, options.endpoints, options.timeout, options.heartbeat) if err != nil { logger.Warnf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}", options.name, options.endpoints, options.timeout, err) @@ -118,7 +119,7 @@ func ValidateClient(container clientFacade, opts ...Option) error { // Client lose connection with etcd server if container.Client().rawClient == nil { - newClient, err := newClient(options.name, options.endpoints, options.timeout, options.heartbeat) + newClient, err := NewClient(options.name, options.endpoints, options.timeout, options.heartbeat) if err != nil { logger.Warnf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}", options.name, options.endpoints, options.timeout, err) @@ -130,6 +131,26 @@ func ValidateClient(container clientFacade, opts ...Option) error { return nil } +// nolint +func NewServiceDiscoveryClient(opts ...Option) *Client { + options := &Options{ + heartbeat: 1, // default heartbeat + } + + for _, opt := range opts { + opt(options) + } + + newClient, err := NewClient(options.name, options.endpoints, options.timeout, options.heartbeat) + if err != nil { + logger.Errorf("new etcd client (name{%s}, etcd addresses{%v}, timeout{%d}) = error{%v}", + options.name, options.endpoints, options.timeout, err) + return nil + } + + return newClient +} + // Client represents etcd client Configuration type Client struct { lock sync.RWMutex @@ -148,7 +169,8 @@ type Client struct { Wait sync.WaitGroup } -func newClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) { +// nolint +func NewClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) { ctx, cancel := context.WithCancel(context.Background()) rawClient, err := clientv3.New(clientv3.Config{ @@ -285,6 +307,28 @@ func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error { return nil } +// if k not exist will put k/v in etcd +// if k is already exist in etcd, replace it +func (c *Client) update(k string, v string, opts ...clientv3.OpOption) error { + + c.lock.RLock() + defer c.lock.RUnlock() + + if c.rawClient == nil { + return ErrNilETCDV3Client + } + + _, err := c.rawClient.Txn(c.ctx). + If(clientv3.Compare(clientv3.Version(k), "!=", -1)). + Then(clientv3.OpPut(k, v, opts...)). + Commit() + if err != nil { + return err + + } + return nil +} + func (c *Client) delete(k string) error { c.lock.RLock() @@ -455,6 +499,15 @@ func (c *Client) Create(k string, v string) error { return nil } +// Update key value ... +func (c *Client) Update(k, v string) error { + err := c.update(k, v) + if err != nil { + return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) + } + return nil +} + // nolint func (c *Client) Delete(k string) error { @@ -467,16 +520,14 @@ func (c *Client) Delete(k string) error { } // RegisterTemp registers a temporary node -func (c *Client) RegisterTemp(basePath string, node string) (string, error) { +func (c *Client) RegisterTemp(k, v string) error { - completeKey := path.Join(basePath, node) - - err := c.keepAliveKV(completeKey, "") + err := c.keepAliveKV(k, v) if err != nil { - return "", perrors.WithMessagef(err, "keepalive kv (key %s)", completeKey) + return perrors.WithMessagef(err, "keepalive kv (key %s)", k) } - return completeKey, nil + return nil } // GetChildrenKVList gets children kv list by @k diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go index e37b6383df55f1c7e7b64be62fc2eb22d1034616..3de266f42ffbc69a1e2ba4662a9a9fff1d831cd4 100644 --- a/remoting/etcdv3/client_test.go +++ b/remoting/etcdv3/client_test.go @@ -120,7 +120,7 @@ func (suite *ClientTestSuite) TearDownSuite() { } func (suite *ClientTestSuite) setUpClient() *Client { - c, err := newClient(suite.etcdConfig.name, + c, err := NewClient(suite.etcdConfig.name, suite.etcdConfig.endpoints, suite.etcdConfig.timeout, suite.etcdConfig.heartbeat) @@ -154,7 +154,7 @@ func (suite *ClientTestSuite) TestClientValid() { c := suite.client t := suite.T() - if c.Valid() != true { + if !c.Valid() { t.Fatal("client is not valid") } c.Close() @@ -174,7 +174,7 @@ func (suite *ClientTestSuite) TestClientDone() { c.Wait.Wait() - if c.Valid() == true { + if c.Valid() { suite.T().Fatal("client should be invalid then") } } @@ -384,7 +384,7 @@ func (suite *ClientTestSuite) TestClientRegisterTemp() { assert.Contains(t, events, eDelete) }() - _, err := c.RegisterTemp("scott", "wang") + err := c.RegisterTemp("scott/wang", "test") if err != nil { t.Fatal(err) } diff --git a/remoting/etcdv3/facade.go b/remoting/etcdv3/facade.go index 3f5999fdf3c5a0791d780e8f5521ef3ea51e9372..2edbb6650890d6d655d8356e1c0a2979a022f0a8 100644 --- a/remoting/etcdv3/facade.go +++ b/remoting/etcdv3/facade.go @@ -85,10 +85,8 @@ LOOP: ) logger.Infof("ETCDV3ProviderRegistry.validateETCDV3Client(etcd Addr{%s}) = error{%#v}", endpoint, perrors.WithStack(err)) - if err == nil { - if r.RestartCallBack() { - break - } + if err == nil && r.RestartCallBack() { + break } failTimes++ if MaxFailTimes <= failTimes { diff --git a/remoting/etcdv3/listener.go b/remoting/etcdv3/listener.go index 00b5b19b36d3baa8871efdd3d53e80f05d7aeac1..4f80a89dfb713036a5d4d812bc7a2d5551f42284 100644 --- a/remoting/etcdv3/listener.go +++ b/remoting/etcdv3/listener.go @@ -49,7 +49,7 @@ func NewEventListener(client *Client) *EventListener { } } -// ListenServiceNodeEvent Listen on a spec key +// listenServiceNodeEvent Listen on a spec key // this method will return true when spec key deleted, // this method will return false when deep layer connection lose func (l *EventListener) ListenServiceNodeEvent(key string, listener ...remoting.DataListener) bool { @@ -178,9 +178,9 @@ func timeSecondDuration(sec int) time.Duration { } // ListenServiceEvent is invoked by etcdv3 ConsumerRegistry::Registe/ etcdv3 ConsumerRegistry::get/etcdv3 ConsumerRegistry::getListener -// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent +// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> listenServiceNodeEvent // | -// --------> ListenServiceNodeEvent +// --------> listenServiceNodeEvent func (l *EventListener) ListenServiceEvent(key string, listener remoting.DataListener) { l.keyMapLock.Lock() diff --git a/remoting/kubernetes/client_test.go b/remoting/kubernetes/client_test.go index e116c48b1aa85b9b2331045dfbc42891aee1f2e1..d6c5a2e88057459c0a87cd9a607e9c10970b07b2 100644 --- a/remoting/kubernetes/client_test.go +++ b/remoting/kubernetes/client_test.go @@ -59,6 +59,9 @@ var tests = []struct { // test dataset prefix const prefix = "name" +var ( + watcherStopLog = "the watcherSet watcher was stopped" +) var clientPodListJsonData = `{ "apiVersion": "v1", "items": [ @@ -258,12 +261,12 @@ func TestClientValid(t *testing.T) { client := getTestClient(t) defer client.Close() - if client.Valid() != true { + if !client.Valid() { t.Fatal("client is not valid") } client.Close() - if client.Valid() != false { + if client.Valid() { t.Fatal("client is valid") } } @@ -278,7 +281,7 @@ func TestClientDone(t *testing.T) { <-client.Done() - if client.Valid() == true { + if client.Valid() { t.Fatal("client should be invalid") } } @@ -331,7 +334,7 @@ func TestClientGetChildrenKVList(t *testing.T) { return } case <-done: - t.Log("the watcherSet watcher was stopped") + t.Log(watcherStopLog) return } } @@ -399,7 +402,7 @@ func TestClientWatchPrefix(t *testing.T) { case e := <-wc: t.Logf("got event %v k %s v %s", e.EventType, e.Key, e.Value) case <-done: - t.Log("the watcherSet watcher was stopped") + t.Log(watcherStopLog) return } } @@ -441,7 +444,7 @@ func TestClientWatch(t *testing.T) { case e := <-wc: t.Logf("got event %v k %s v %s", e.EventType, e.Key, e.Value) case <-done: - t.Log("the watcherSet watcher was stopped") + t.Log(watcherStopLog) return } } diff --git a/remoting/kubernetes/facade_test.go b/remoting/kubernetes/facade_test.go index 4323c0ec565d64f445f15c3e5c265451b2b87ce7..65c5d715a38bd0862245255e0276ff5e959de3a3 100644 --- a/remoting/kubernetes/facade_test.go +++ b/remoting/kubernetes/facade_test.go @@ -48,6 +48,7 @@ func (r *mockFacade) GetUrl() common.URL { } func (r *mockFacade) Destroy() { + // TODO implementation me } func (r *mockFacade) RestartCallBack() bool { diff --git a/remoting/listener.go b/remoting/listener.go index 6cbb883181ff8ec1c9124f8d8cc3d7ec0920abd9..eb27c71dfd64a1063927663a9817f8f23b85dd20 100644 --- a/remoting/listener.go +++ b/remoting/listener.go @@ -48,6 +48,7 @@ var serviceEventTypeStrings = [...]string{ "update", } +// nolint func (t EventType) String() string { return serviceEventTypeStrings[t] } @@ -63,6 +64,7 @@ type Event struct { Content string } +// nolint func (e Event) String() string { return fmt.Sprintf("Event{Action{%s}, Content{%s}}", e.Action, e.Content) } diff --git a/registry/nacos/base_registry.go b/remoting/nacos/builder.go similarity index 58% rename from registry/nacos/base_registry.go rename to remoting/nacos/builder.go index 63f4999675470853d0f48d1a22b709efdc1c9d26..8a247e267daa02d748c9a5e47ced698f617dfe9a 100644 --- a/registry/nacos/base_registry.go +++ b/remoting/nacos/builder.go @@ -26,6 +26,7 @@ import ( import ( "github.com/nacos-group/nacos-sdk-go/clients" + "github.com/nacos-group/nacos-sdk-go/clients/config_client" "github.com/nacos-group/nacos-sdk-go/clients/naming_client" nacosConstant "github.com/nacos-group/nacos-sdk-go/common/constant" perrors "github.com/pkg/errors" @@ -34,30 +35,16 @@ import ( import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/config" ) -// baseRegistry is the parent of both interface-level registry -// and service discovery(related to application-level registry) -type nacosBaseRegistry struct { - *common.URL - namingClient naming_client.INamingClient -} - -// newBaseRegistry will create new instance -func newBaseRegistry(url *common.URL) (nacosBaseRegistry, error) { +// NewNacosConfigClient read the config from url and build an instance +func NewNacosConfigClient(url *common.URL) (config_client.IConfigClient, error) { nacosConfig, err := getNacosConfig(url) if err != nil { - return nacosBaseRegistry{}, err - } - client, err := clients.CreateNamingClient(nacosConfig) - if err != nil { - return nacosBaseRegistry{}, err - } - registry := nacosBaseRegistry{ - URL: url, - namingClient: client, + return nil, err } - return registry, nil + return clients.CreateConfigClient(nacosConfig) } // getNacosConfig will return the nacos config @@ -95,8 +82,49 @@ func getNacosConfig(url *common.URL) (map[string]interface{}, error) { clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "") clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "") clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "") + clientConfig.NamespaceId = url.GetParam(constant.NACOS_NAMESPACE_ID, "") + clientConfig.Username = url.GetParam(constant.NACOS_USERNAME, "") + clientConfig.Password = url.GetParam(constant.NACOS_PASSWORD, "") + clientConfig.NamespaceId = url.GetParam(constant.NACOS_NAMESPACE_ID, "") clientConfig.NotLoadCacheAtStart = true configMap["clientConfig"] = clientConfig return configMap, nil } + +// NewNacosClient creates an instance with the config +func NewNacosClient(rc *config.RemoteConfig) (naming_client.INamingClient, error) { + if len(rc.Address) == 0 { + return nil, perrors.New("nacos address is empty!") + } + configMap := make(map[string]interface{}, 2) + + addresses := strings.Split(rc.Address, ",") + serverConfigs := make([]nacosConstant.ServerConfig, 0, len(addresses)) + for _, addr := range addresses { + ip, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, perrors.WithMessagef(err, "split [%s] ", addr) + } + port, _ := strconv.Atoi(portStr) + serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{ + IpAddr: ip, + Port: uint64(port), + }) + } + configMap["serverConfigs"] = serverConfigs + + var clientConfig nacosConstant.ClientConfig + timeout := rc.Timeout() + clientConfig.TimeoutMs = uint64(timeout.Nanoseconds() / constant.MsToNanoRate) + clientConfig.ListenInterval = 2 * clientConfig.TimeoutMs + clientConfig.CacheDir = rc.GetParam(constant.NACOS_CACHE_DIR_KEY, "") + clientConfig.LogDir = rc.GetParam(constant.NACOS_LOG_DIR_KEY, "") + clientConfig.Endpoint = rc.Address + clientConfig.Username = rc.Username + clientConfig.Password = rc.Password + clientConfig.NotLoadCacheAtStart = true + configMap["clientConfig"] = clientConfig + + return clients.CreateNamingClient(configMap) +} diff --git a/metadata/namemapping/memory/service_name_mapping.go b/remoting/nacos/builder_test.go similarity index 64% rename from metadata/namemapping/memory/service_name_mapping.go rename to remoting/nacos/builder_test.go index 8a891491bdb97808b77422092a1043c1c0ffafbf..61d13ef26f9f1d17173bbeb11468f9babdade2f5 100644 --- a/metadata/namemapping/memory/service_name_mapping.go +++ b/remoting/nacos/builder_test.go @@ -15,22 +15,35 @@ * limitations under the License. */ -package memory +package nacos import ( - gxset "github.com/dubbogo/gost/container/set" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" ) import ( "github.com/apache/dubbo-go/config" ) -type InMemoryServiceNameMapping struct{} +func TestNewNacosClient(t *testing.T) { + rc := &config.RemoteConfig{} + client, err := NewNacosClient(rc) -func (i InMemoryServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error { - return nil -} + // address is nil + assert.NotNil(t, err) + + rc.Address = "console.nacos.io:80:123" + client, err = NewNacosClient(rc) + // invalid address + assert.NotNil(t, err) -func (i InMemoryServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) { - return gxset.NewSet(config.GetApplicationConfig().Name), nil + rc.Address = "console.nacos.io:80" + rc.TimeoutStr = "10s" + client, err = NewNacosClient(rc) + assert.NotNil(t, client) + assert.Nil(t, err) } diff --git a/remoting/zookeeper/client.go b/remoting/zookeeper/client.go index a8ad41846eaf933f0410bbb1ae44aef893c79d92..4ca34a6aeccf7b588a96edb44b4d5913a3e0fd8e 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -42,21 +42,23 @@ const ( ) var ( - errNilZkClientConn = perrors.New("zookeeperclient{conn} is nil") + errNilZkClientConn = perrors.New("zookeeper client{conn} is nil") errNilChildren = perrors.Errorf("has none children") errNilNode = perrors.Errorf("node does not exist") ) // ZookeeperClient represents zookeeper client Configuration type ZookeeperClient struct { - name string - ZkAddrs []string - sync.Mutex // for conn - Conn *zk.Conn - Timeout time.Duration - exit chan struct{} - Wait sync.WaitGroup - eventRegistry map[string][]*chan struct{} + name string + ZkAddrs []string + sync.RWMutex // for conn + Conn *zk.Conn + Timeout time.Duration + exit chan struct{} + Wait sync.WaitGroup + + eventRegistry map[string][]*chan struct{} + eventRegistryLock sync.RWMutex } // nolint @@ -108,14 +110,15 @@ func WithZkName(name string) Option { } // ValidateZookeeperClient validates client and sets options -func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error { - var err error +func ValidateZookeeperClient(container ZkClientFacade, opts ...Option) error { + var ( + err error + ) options := &Options{} for _, opt := range opts { opt(options) } connected := false - err = nil lock := container.ZkClientLock() url := container.GetUrl() @@ -124,16 +127,15 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error { defer lock.Unlock() if container.ZkClient() == nil { - //in dubbo ,every registry only connect one node ,so this is []string{r.Address} - var timeout time.Duration - timeout, err = time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + // in dubbo, every registry only connect one node, so this is []string{r.Address} + timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) if err != nil { logger.Errorf("timeout config %v is invalid ,err is %v", url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT), err.Error()) return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location) } zkAddresses := strings.Split(url.Location, ",") - newClient, err := newZookeeperClient(options.zkName, zkAddresses, timeout) + newClient, err := NewZookeeperClient(options.zkName, zkAddresses, timeout) if err != nil { logger.Warnf("newZookeeperClient(name{%s}, zk address{%v}, timeout{%d}) = error{%v}", options.zkName, url.Location, timeout.String(), err) @@ -154,14 +156,15 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error { } if connected { - logger.Info("Connect to zookeeper successfully, name{%s}, zk address{%v}", options.zkName, url.Location) - container.WaitGroup().Add(1) //zk client start successful, then registry wg +1 + logger.Infof("Connect to zookeeper successfully, name{%s}, zk address{%v}", options.zkName, url.Location) + container.WaitGroup().Add(1) // zk client start successful, then registry wg +1 } return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.PrimitiveURL) } -func newZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (*ZookeeperClient, error) { +// nolint +func NewZookeeperClient(name string, zkAddrs []string, timeout time.Duration) (*ZookeeperClient, error) { var ( err error event <-chan zk.Event @@ -211,14 +214,14 @@ func NewMockZookeeperClient(name string, timeout time.Duration, opts ...Option) eventRegistry: make(map[string][]*chan struct{}), } - opions := &Options{} + options := &Options{} for _, opt := range opts { - opt(opions) + opt(options) } // connect to zookeeper - if opions.ts != nil { - ts = opions.ts + if options.ts != nil { + ts = options.ts } else { ts, err = zk.StartTestCluster(1, nil, nil) if err != nil { @@ -246,13 +249,12 @@ func (z *ZookeeperClient) HandleZkEvent(session <-chan zk.Event) { logger.Infof("zk{path:%v, name:%s} connection goroutine game over.", z.ZkAddrs, z.name) }() -LOOP: for { select { case <-z.exit: - break LOOP + return case event = <-session: - logger.Warnf("client{%s} get a zookeeper event{type:%s, server:%s, path:%s, state:%d-%s, err:%v}", + logger.Infof("client{%s} get a zookeeper event{type:%s, server:%s, path:%s, state:%d-%s, err:%v}", z.name, event.Type, event.Server, event.Path, event.State, StateToString(event.State), event.Err) switch (int)(event.State) { case (int)(zk.StateDisconnected): @@ -265,11 +267,10 @@ LOOP: if conn != nil { conn.Close() } - - break LOOP + return case (int)(zk.EventNodeDataChanged), (int)(zk.EventNodeChildrenChanged): logger.Infof("zkClient{%s} get zk node changed event{path:%s}", z.name, event.Path) - z.Lock() + z.eventRegistryLock.RLock() for p, a := range z.eventRegistry { if strings.HasPrefix(p, event.Path) { logger.Infof("send event{state:zk.EventNodeDataChange, Path:%s} notify event to path{%s} related listener", @@ -279,16 +280,18 @@ LOOP: } } } - z.Unlock() + z.eventRegistryLock.RUnlock() case (int)(zk.StateConnecting), (int)(zk.StateConnected), (int)(zk.StateHasSession): if state == (int)(zk.StateHasSession) { continue } + z.eventRegistryLock.RLock() if a, ok := z.eventRegistry[event.Path]; ok && 0 < len(a) { for _, e := range a { *e <- struct{}{} } } + z.eventRegistryLock.RUnlock() } state = (int)(event.State) } @@ -301,13 +304,12 @@ func (z *ZookeeperClient) RegisterEvent(zkPath string, event *chan struct{}) { return } - z.Lock() + z.eventRegistryLock.Lock() + defer z.eventRegistryLock.Unlock() a := z.eventRegistry[zkPath] a = append(a, event) - z.eventRegistry[zkPath] = a logger.Debugf("zkClient{%s} register event{path:%s, ptr:%p}", z.name, zkPath, event) - z.Unlock() } // UnregisterEvent unregisters zookeeper events @@ -315,16 +317,16 @@ func (z *ZookeeperClient) UnregisterEvent(zkPath string, event *chan struct{}) { if zkPath == "" { return } - z.Lock() - defer z.Unlock() + + z.eventRegistryLock.Lock() + defer z.eventRegistryLock.Unlock() infoList, ok := z.eventRegistry[zkPath] if !ok { return } for i, e := range infoList { if e == event { - arr := infoList - infoList = append(arr[:i], arr[i+1:]...) + infoList = append(infoList[:i], infoList[i+1:]...) logger.Infof("zkClient{%s} unregister event{path:%s, event:%p}", z.name, zkPath, event) } } @@ -362,11 +364,11 @@ func (z *ZookeeperClient) ZkConnValid() bool { } valid := true - z.Lock() + z.RLock() if z.Conn == nil { valid = false } - z.Unlock() + z.RUnlock() return valid } @@ -384,11 +386,11 @@ func (z *ZookeeperClient) Close() { z.Conn = nil z.Unlock() if conn != nil { - logger.Warnf("zkClient Conn{name:%s, zk addr:%s} exit now.", z.name, conn.SessionID()) + logger.Infof("zkClient Conn{name:%s, zk addr:%d} exit now.", z.name, conn.SessionID()) conn.Close() } - logger.Warnf("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.ZkAddrs) + logger.Infof("zkClient{name:%s, zk addr:%s} exit now.", z.name, z.ZkAddrs) } // Create will create the node recursively, which means that if the parent node is absent, @@ -407,21 +409,21 @@ func (z *ZookeeperClient) CreateWithValue(basePath string, value []byte) error { ) logger.Debugf("zookeeperClient.Create(basePath{%s})", basePath) + conn := z.getConn() + err = errNilZkClientConn + if conn == nil { + return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath) + } + for _, str := range strings.Split(basePath, "/")[1:] { tmpPath = path.Join(tmpPath, "/", str) - err = errNilZkClientConn - z.Lock() - conn := z.Conn - z.Unlock() - if conn != nil { - _, err = conn.Create(tmpPath, value, 0, zk.WorldACL(zk.PermAll)) - } + _, err = conn.Create(tmpPath, value, 0, zk.WorldACL(zk.PermAll)) if err != nil { if err == zk.ErrNodeExists { - logger.Debugf("zk.create(\"%s\") exists\n", tmpPath) + logger.Debugf("zk.create(\"%s\") exists", tmpPath) } else { - logger.Errorf("zk.create(\"%s\") error(%v)\n", tmpPath, perrors.WithStack(err)) + logger.Errorf("zk.create(\"%s\") error(%v)", tmpPath, perrors.WithStack(err)) return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath) } } @@ -430,16 +432,52 @@ func (z *ZookeeperClient) CreateWithValue(basePath string, value []byte) error { return nil } -// nolint -func (z *ZookeeperClient) Delete(basePath string) error { +// CreateTempWithValue will create the node recursively, which means that if the parent node is absent, +// it will create parent node first锛宎nd set value in last child path +// If the path exist, it will update data +func (z *ZookeeperClient) CreateTempWithValue(basePath string, value []byte) error { var ( - err error + err error + tmpPath string ) + logger.Debugf("zookeeperClient.Create(basePath{%s})", basePath) + conn := z.getConn() err = errNilZkClientConn - z.Lock() - conn := z.Conn - z.Unlock() + if conn == nil { + return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath) + } + + pathSlice := strings.Split(basePath, "/")[1:] + length := len(pathSlice) + for i, str := range pathSlice { + tmpPath = path.Join(tmpPath, "/", str) + // last child need be ephemeral + if i == length-1 { + _, err = conn.Create(tmpPath, value, zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) + if err == zk.ErrNodeExists { + return err + } + } else { + _, err = conn.Create(tmpPath, []byte{}, 0, zk.WorldACL(zk.PermAll)) + } + if err != nil { + if err == zk.ErrNodeExists { + logger.Debugf("zk.create(\"%s\") exists", tmpPath) + } else { + logger.Errorf("zk.create(\"%s\") error(%v)", tmpPath, perrors.WithStack(err)) + return perrors.WithMessagef(err, "zk.Create(path:%s)", basePath) + } + } + } + + return nil +} + +// nolint +func (z *ZookeeperClient) Delete(basePath string) error { + err := errNilZkClientConn + conn := z.getConn() if conn != nil { err = conn.Delete(basePath, -1) } @@ -451,26 +489,22 @@ func (z *ZookeeperClient) Delete(basePath string) error { func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, error) { var ( err error - data []byte zkPath string tmpPath string ) err = errNilZkClientConn - data = []byte("") zkPath = path.Join(basePath) + "/" + node - z.Lock() - conn := z.Conn - z.Unlock() + conn := z.getConn() if conn != nil { - tmpPath, err = conn.Create(zkPath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) + tmpPath, err = conn.Create(zkPath, []byte(""), zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) } if err != nil { - logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)\n", zkPath, perrors.WithStack(err)) + logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)", zkPath, perrors.WithStack(err)) return zkPath, perrors.WithStack(err) } - logger.Debugf("zkClient{%s} create a temp zookeeper node:%s\n", z.name, tmpPath) + logger.Debugf("zkClient{%s} create a temp zookeeper node:%s", z.name, tmpPath) return tmpPath, nil } @@ -483,9 +517,7 @@ func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, ) err = errNilZkClientConn - z.Lock() - conn := z.Conn - z.Unlock() + conn := z.getConn() if conn != nil { tmpPath, err = conn.Create( path.Join(basePath)+"/", @@ -497,11 +529,11 @@ func (z *ZookeeperClient) RegisterTempSeq(basePath string, data []byte) (string, logger.Debugf("zookeeperClient.RegisterTempSeq(basePath{%s}) = tempPath{%s}", basePath, tmpPath) if err != nil && err != zk.ErrNodeExists { - logger.Errorf("zkClient{%s} conn.Create(\"%s\", \"%s\", zk.FlagEphemeral|zk.FlagSequence) error(%v)\n", + logger.Errorf("zkClient{%s} conn.Create(\"%s\", \"%s\", zk.FlagEphemeral|zk.FlagSequence) error(%v)", z.name, basePath, string(data), err) return "", perrors.WithStack(err) } - logger.Debugf("zkClient{%s} create a temp zookeeper node:%s\n", z.name, tmpPath) + logger.Debugf("zkClient{%s} create a temp zookeeper node:%s", z.name, tmpPath) return tmpPath, nil } @@ -516,9 +548,7 @@ func (z *ZookeeperClient) GetChildrenW(path string) ([]string, <-chan zk.Event, ) err = errNilZkClientConn - z.Lock() - conn := z.Conn - z.Unlock() + conn := z.getConn() if conn != nil { children, stat, watcher, err = conn.ChildrenW(path) } @@ -552,9 +582,7 @@ func (z *ZookeeperClient) GetChildren(path string) ([]string, error) { ) err = errNilZkClientConn - z.Lock() - conn := z.Conn - z.Unlock() + conn := z.getConn() if conn != nil { children, stat, err = conn.Children(path) } @@ -585,9 +613,7 @@ func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) { ) err = errNilZkClientConn - z.Lock() - conn := z.Conn - z.Unlock() + conn := z.getConn() if conn != nil { exist, _, watcher, err = conn.ExistsW(zkPath) } @@ -608,3 +634,15 @@ func (z *ZookeeperClient) ExistW(zkPath string) (<-chan zk.Event, error) { func (z *ZookeeperClient) GetContent(zkPath string) ([]byte, *zk.Stat, error) { return z.Conn.Get(zkPath) } + +// nolint +func (z *ZookeeperClient) SetContent(zkPath string, content []byte, version int32) (*zk.Stat, error) { + return z.Conn.Set(zkPath, content, version) +} + +// getConn gets zookeeper connection safely +func (z *ZookeeperClient) getConn() *zk.Conn { + z.RLock() + defer z.RUnlock() + return z.Conn +} diff --git a/remoting/zookeeper/client_test.go b/remoting/zookeeper/client_test.go index 0f6899568ad4744dc58022c41e22db6f901ad5de..34741700ca2a9d86ee5321b0b19ed64b2b1a25a8 100644 --- a/remoting/zookeeper/client_test.go +++ b/remoting/zookeeper/client_test.go @@ -18,7 +18,6 @@ package zookeeper import ( - "fmt" "testing" "time" ) @@ -28,6 +27,10 @@ import ( "github.com/stretchr/testify/assert" ) +import ( + "github.com/apache/dubbo-go/common/logger" +) + func verifyEventStateOrder(t *testing.T, c <-chan zk.Event, expectedStates []zk.State, source string) { for _, state := range expectedStates { for { @@ -35,7 +38,7 @@ func verifyEventStateOrder(t *testing.T, c <-chan zk.Event, expectedStates []zk. if !ok { t.Fatalf("unexpected channel close for %s", source) } - fmt.Println(event) + logger.Debug(event) if event.Type != zk.EventSession { continue } @@ -87,9 +90,10 @@ func Test_newMockZookeeperClient(t *testing.T) { } func TestCreate(t *testing.T) { - ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) + ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) defer ts.Stop() - err := z.Create("test1/test2/test3/test4") + err = z.Create("test1/test2/test3/test4") assert.NoError(t, err) states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession} @@ -97,21 +101,24 @@ func TestCreate(t *testing.T) { } func TestCreateDelete(t *testing.T) { - ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) + ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) defer ts.Stop() states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession} verifyEventStateOrder(t, event, states, "event channel") - err := z.Create("/test1/test2/test3/test4") + err = z.Create("/test1/test2/test3/test4") + assert.NoError(t, err) + err = z.Delete("/test1/test2/test3/test4") assert.NoError(t, err) - err2 := z.Delete("/test1/test2/test3/test4") - assert.NoError(t, err2) + // verifyEventOrder(t, event, []zk.EventType{zk.EventNodeCreated}, "event channel") } func TestRegisterTemp(t *testing.T) { - ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) + ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) defer ts.Stop() - err := z.Create("/test1/test2/test3") + err = z.Create("/test1/test2/test3") assert.NoError(t, err) tmpath, err := z.RegisterTemp("/test1/test2/test3", "test4") @@ -122,9 +129,10 @@ func TestRegisterTemp(t *testing.T) { } func TestRegisterTempSeq(t *testing.T) { - ts, z, event, _ := NewMockZookeeperClient("test", 15*time.Second) + ts, z, event, err := NewMockZookeeperClient("test", 15*time.Second) + assert.NoError(t, err) defer ts.Stop() - err := z.Create("/test1/test2/test3") + err = z.Create("/test1/test2/test3") assert.NoError(t, err) tmpath, err := z.RegisterTempSeq("/test1/test2/test3", []byte("test")) assert.NoError(t, err) diff --git a/remoting/zookeeper/curator_discovery/service_discovery.go b/remoting/zookeeper/curator_discovery/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..9566b5494389325520b4eb6a8eb170e0b305bb47 --- /dev/null +++ b/remoting/zookeeper/curator_discovery/service_discovery.go @@ -0,0 +1,279 @@ +/* + * 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 curator_discovery + +import ( + "encoding/json" + "path" + "strings" + "sync" +) + +import ( + "github.com/dubbogo/go-zookeeper/zk" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/zookeeper" +) + +// Entry contain a service instance +type Entry struct { + sync.Mutex + instance *ServiceInstance +} + +// ServiceInstance which define in curator-x-discovery, please refer to +// https://github.com/apache/curator/blob/master/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceDiscovery.java +// It's not exactly the same as curator-x-discovery's service discovery +type ServiceDiscovery struct { + client *zookeeper.ZookeeperClient + mutex *sync.Mutex + basePath string + services *sync.Map + listener *zookeeper.ZkEventListener +} + +// NewServiceDiscovery the constructor of service discovery +func NewServiceDiscovery(client *zookeeper.ZookeeperClient, basePath string) *ServiceDiscovery { + return &ServiceDiscovery{ + client: client, + mutex: &sync.Mutex{}, + basePath: basePath, + services: &sync.Map{}, + listener: zookeeper.NewZkEventListener(client), + } +} + +// registerService register service to zookeeper +func (sd *ServiceDiscovery) registerService(instance *ServiceInstance) error { + path := sd.pathForInstance(instance.Name, instance.Id) + data, err := json.Marshal(instance) + if err != nil { + return err + } + err = sd.client.CreateTempWithValue(path, data) + if err == zk.ErrNodeExists { + _, state, _ := sd.client.GetContent(path) + if state != nil { + _, err = sd.client.SetContent(path, data, state.Version+1) + if err != nil { + logger.Debugf("Try to update the node data failed. In most cases, it's not a problem. ") + } + } + return nil + } + if err != nil { + return err + } + return nil +} + +// RegisterService register service to zookeeper, and ensure cache is consistent with zookeeper +func (sd *ServiceDiscovery) RegisterService(instance *ServiceInstance) error { + value, loaded := sd.services.LoadOrStore(instance.Id, &Entry{}) + entry, ok := value.(*Entry) + if !ok { + return perrors.New("[ServiceDiscovery] services value not entry") + } + entry.Lock() + defer entry.Unlock() + entry.instance = instance + err := sd.registerService(instance) + if err != nil { + return err + } + if !loaded { + sd.ListenServiceInstanceEvent(instance.Name, instance.Id, sd) + } + return nil +} + +// UpdateService update service in zookeeper, and ensure cache is consistent with zookeeper +func (sd *ServiceDiscovery) UpdateService(instance *ServiceInstance) error { + value, ok := sd.services.Load(instance.Id) + if !ok { + return perrors.Errorf("[ServiceDiscovery] Service{%s} not registered", instance.Id) + } + entry, ok := value.(*Entry) + if !ok { + return perrors.New("[ServiceDiscovery] services value not entry") + } + data, err := json.Marshal(instance) + + if err != nil { + return err + } + + entry.Lock() + defer entry.Unlock() + entry.instance = instance + path := sd.pathForInstance(instance.Name, instance.Id) + + _, err = sd.client.SetContent(path, data, -1) + if err != nil { + return err + } + return nil +} + +// updateInternalService update service in cache +func (sd *ServiceDiscovery) updateInternalService(name, id string) { + value, ok := sd.services.Load(id) + if !ok { + return + } + entry, ok := value.(*Entry) + if !ok { + return + } + entry.Lock() + defer entry.Unlock() + instance, err := sd.QueryForInstance(name, id) + if err != nil { + logger.Infof("[zkServiceDiscovery] UpdateInternalService{%s} error = err{%v}", id, err) + return + } + entry.instance = instance + return +} + +// UnregisterService un-register service in zookeeper and delete service in cache +func (sd *ServiceDiscovery) UnregisterService(instance *ServiceInstance) error { + _, ok := sd.services.Load(instance.Id) + if !ok { + return nil + } + sd.services.Delete(instance.Id) + return sd.unregisterService(instance) +} + +// unregisterService un-register service in zookeeper +func (sd *ServiceDiscovery) unregisterService(instance *ServiceInstance) error { + path := sd.pathForInstance(instance.Name, instance.Id) + return sd.client.Delete(path) +} + +// ReRegisterServices re-register all cache services to zookeeper +func (sd *ServiceDiscovery) ReRegisterServices() { + sd.services.Range(func(key, value interface{}) bool { + entry, ok := value.(*Entry) + if !ok { + return true + } + entry.Lock() + defer entry.Unlock() + instance := entry.instance + err := sd.registerService(instance) + if err != nil { + logger.Errorf("[zkServiceDiscovery] registerService{%s} error = err{%v}", instance.Id, perrors.WithStack(err)) + return true + } + sd.ListenServiceInstanceEvent(instance.Name, instance.Id, sd) + return true + }) +} + +// QueryForInstances query instances in zookeeper by name +func (sd *ServiceDiscovery) QueryForInstances(name string) ([]*ServiceInstance, error) { + ids, err := sd.client.GetChildren(sd.pathForName(name)) + if err != nil { + return nil, err + } + var ( + instance *ServiceInstance + instances []*ServiceInstance + ) + for _, id := range ids { + instance, err = sd.QueryForInstance(name, id) + if err != nil { + return nil, err + } + instances = append(instances, instance) + } + return instances, nil +} + +// QueryForInstance query instances in zookeeper by name and id +func (sd *ServiceDiscovery) QueryForInstance(name string, id string) (*ServiceInstance, error) { + path := sd.pathForInstance(name, id) + data, _, err := sd.client.GetContent(path) + if err != nil { + return nil, err + } + instance := &ServiceInstance{} + err = json.Unmarshal(data, instance) + if err != nil { + return nil, err + } + return instance, nil +} + +// QueryForInstance query all service name in zookeeper +func (sd *ServiceDiscovery) QueryForNames() ([]string, error) { + return sd.client.GetChildren(sd.basePath) +} + +// ListenServiceEvent add a listener in a service +func (sd *ServiceDiscovery) ListenServiceEvent(name string, listener remoting.DataListener) { + sd.listener.ListenServiceEvent(nil, sd.pathForName(name), listener) +} + +// ListenServiceEvent add a listener in a instance +func (sd *ServiceDiscovery) ListenServiceInstanceEvent(name, id string, listener remoting.DataListener) { + sd.listener.ListenServiceNodeEvent(sd.pathForInstance(name, id), listener) +} + +// DataChange implement DataListener's DataChange function +func (sd *ServiceDiscovery) DataChange(eventType remoting.Event) bool { + path := eventType.Path + name, id, err := sd.getNameAndId(path) + if err != nil { + logger.Errorf("[ServiceDiscovery] data change error = {%v}", err) + return true + } + sd.updateInternalService(name, id) + return true +} + +// getNameAndId get service name and instance id by path +func (sd *ServiceDiscovery) getNameAndId(path string) (string, string, error) { + path = strings.TrimPrefix(path, sd.basePath) + path = strings.TrimPrefix(path, constant.PATH_SEPARATOR) + pathSlice := strings.Split(path, constant.PATH_SEPARATOR) + if len(pathSlice) < 2 { + return "", "", perrors.Errorf("[ServiceDiscovery] path{%s} dont contain name and id", path) + } + name := pathSlice[0] + id := pathSlice[1] + return name, id, nil +} + +// nolint +func (sd *ServiceDiscovery) pathForInstance(name, id string) string { + return path.Join(sd.basePath, name, id) +} + +// nolint +func (sd *ServiceDiscovery) pathForName(name string) string { + return path.Join(sd.basePath, name) +} diff --git a/remoting/zookeeper/curator_discovery/service_instance.go b/remoting/zookeeper/curator_discovery/service_instance.go new file mode 100644 index 0000000000000000000000000000000000000000..f8d2bc723e0e0dd90ffdaa6ccd7c9908d65ac9a0 --- /dev/null +++ b/remoting/zookeeper/curator_discovery/service_instance.go @@ -0,0 +1,29 @@ +/* + * 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 curator_discovery + +// ServiceInstance which define in curator-x-discovery, please refer to +// https://github.com/apache/curator/blob/master/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceInstance.java +type ServiceInstance struct { + Name string + Id string + Address string + Port int + Payload interface{} + RegistrationTimeUTC int64 +} diff --git a/remoting/zookeeper/facade.go b/remoting/zookeeper/facade.go index 10de42523e731d0780ff7132f4655850409135aa..d5d9e6e74858e3ec520aedee5b8ba059baf928d8 100644 --- a/remoting/zookeeper/facade.go +++ b/remoting/zookeeper/facade.go @@ -30,18 +30,18 @@ import ( "github.com/apache/dubbo-go/common/logger" ) -type zkClientFacade interface { +type ZkClientFacade interface { ZkClient() *ZookeeperClient SetZkClient(*ZookeeperClient) ZkClientLock() *sync.Mutex - WaitGroup() *sync.WaitGroup //for wait group control, zk client listener & zk client container - Done() chan struct{} //for zk client control + WaitGroup() *sync.WaitGroup // for wait group control, zk client listener & zk client container + Done() chan struct{} // for zk client control RestartCallBack() bool - common.Node + GetUrl() common.URL } // HandleClientRestart keeps the connection between client and server -func HandleClientRestart(r zkClientFacade) { +func HandleClientRestart(r ZkClientFacade) { var ( err error @@ -78,10 +78,8 @@ LOOP: err = ValidateZookeeperClient(r, WithZkName(zkName)) logger.Infof("ZkProviderRegistry.validateZookeeperClient(zkAddr{%s}) = error{%#v}", zkAddress, perrors.WithStack(err)) - if err == nil { - if r.RestartCallBack() { - break - } + if err == nil && r.RestartCallBack() { + break } failTimes++ if MaxFailTimes <= failTimes { diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go index 01d46da6cc1abae90210a323d32ac84bad80249b..1cd8f064bb15a2ac48b0d62154309b27c55ab946 100644 --- a/remoting/zookeeper/facade_test.go +++ b/remoting/zookeeper/facade_test.go @@ -38,7 +38,7 @@ type mockFacade struct { done chan struct{} } -func newMockFacade(client *ZookeeperClient, url *common.URL) zkClientFacade { +func newMockFacade(client *ZookeeperClient, url *common.URL) ZkClientFacade { mock := &mockFacade{ client: client, URL: url, diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index 2c7f1f84d701d1f1b3994bf84402146de4200c06..9a4874db24696d90e4fcc7d9d987f5888f1be599 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -58,8 +58,20 @@ func (l *ZkEventListener) SetClient(client *ZookeeperClient) { l.client = client } +// ListenServiceNodeEvent listen a path node event +func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener remoting.DataListener) { + // listen l service node + l.wg.Add(1) + go func(zkPath string, listener remoting.DataListener) { + if l.listenServiceNodeEvent(zkPath, listener) { + listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel}) + } + logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) + }(zkPath, listener) +} + // nolint -func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener ...remoting.DataListener) bool { +func (l *ZkEventListener) listenServiceNodeEvent(zkPath string, listener ...remoting.DataListener) bool { defer l.wg.Done() var zkEvent zk.Event for { @@ -77,13 +89,21 @@ func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener ...remo case zk.EventNodeDataChanged: logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeDataChanged}", zkPath) if len(listener) > 0 { - content, _, _ := l.client.Conn.Get(zkEvent.Path) + content, _, err := l.client.Conn.Get(zkEvent.Path) + if err != nil { + logger.Warnf("zk.Conn.Get{key:%s} = error{%v}", zkPath, err) + return false + } listener[0].DataChange(remoting.Event{Path: zkEvent.Path, Action: remoting.EventTypeUpdate, Content: string(content)}) } case zk.EventNodeCreated: logger.Warnf("zk.ExistW(key{%s}) = event{EventNodeCreated}", zkPath) if len(listener) > 0 { - content, _, _ := l.client.Conn.Get(zkEvent.Path) + content, _, err := l.client.Conn.Get(zkEvent.Path) + if err != nil { + logger.Warnf("zk.Conn.Get{key:%s} = error{%v}", zkPath, err) + return false + } listener[0].DataChange(remoting.Event{Path: zkEvent.Path, Action: remoting.EventTypeAdd, Content: string(content)}) } case zk.EventNotWatching: @@ -146,7 +166,7 @@ func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, li l.wg.Add(1) go func(node string, zkPath string, listener remoting.DataListener) { logger.Infof("delete zkNode{%s}", node) - if l.ListenServiceNodeEvent(node, listener) { + if l.listenServiceNodeEvent(node, listener) { logger.Infof("delete content{%s}", node) listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel}) } @@ -227,15 +247,15 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen // Only need to compare Path when subscribing to provider if strings.LastIndex(zkPath, constant.PROVIDER_CATEGORY) != -1 { provider, _ := common.NewURL(c) - if provider.Path != conf.Path { + if provider.ServiceKey() != conf.ServiceKey() { continue } } - //listen l service node + // listen l service node dubboPath := path.Join(zkPath, c) - //Save the path to avoid listen repeatedly + // Save the path to avoid listen repeatedly l.pathMapLock.Lock() _, ok := l.pathMap[dubboPath] l.pathMapLock.Unlock() @@ -247,8 +267,14 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen l.pathMapLock.Lock() l.pathMap[dubboPath] = struct{}{} l.pathMapLock.Unlock() - + // When Zk disconnected, the Conn will be set to nil, so here need check the value of Conn + l.client.RLock() + if l.client.Conn == nil { + l.client.RUnlock() + break + } content, _, err := l.client.Conn.Get(dubboPath) + l.client.RUnlock() if err != nil { logger.Errorf("Get new node path {%v} 's content error,message is {%v}", dubboPath, perrors.WithStack(err)) } @@ -259,14 +285,14 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen logger.Infof("listen dubbo service key{%s}", dubboPath) l.wg.Add(1) go func(zkPath string, listener remoting.DataListener) { - if l.ListenServiceNodeEvent(zkPath) { + if l.listenServiceNodeEvent(zkPath) { listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel}) } logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) }(dubboPath, listener) - //listen sub path recursive - //if zkPath is end of "providers/ & consumers/" we do not listen children dir + // listen sub path recursive + // if zkPath is end of "providers/ & consumers/" we do not listen children dir if strings.LastIndex(zkPath, constant.PROVIDER_CATEGORY) == -1 && strings.LastIndex(zkPath, constant.CONSUMER_CATEGORY) == -1 { l.wg.Add(1) @@ -296,9 +322,9 @@ func timeSecondDuration(sec int) time.Duration { } // ListenServiceEvent is invoked by ZkConsumerRegistry::Register/ZkConsumerRegistry::get/ZkConsumerRegistry::getListener -// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent +// registry.go:Listen -> listenServiceEvent -> listenDirEvent -> listenServiceNodeEvent // | -// --------> ListenServiceNodeEvent +// --------> listenServiceNodeEvent func (l *ZkEventListener) ListenServiceEvent(conf *common.URL, zkPath string, listener remoting.DataListener) { logger.Infof("listen dubbo path{%s}", zkPath) l.wg.Add(1) @@ -312,7 +338,8 @@ func (l *ZkEventListener) valid() bool { return l.client.ZkConnValid() } -// nolint +// Close will let client listen exit func (l *ZkEventListener) Close() { + close(l.client.exit) l.wg.Wait() } diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go index ba7d6ba81b6af97dc5ad3788e8399d08cbe5b2bb..37ef1b4b967d2f6708a4a099875ae90f273ae483 100644 --- a/remoting/zookeeper/listener_test.go +++ b/remoting/zookeeper/listener_test.go @@ -32,6 +32,10 @@ import ( "github.com/apache/dubbo-go/remoting" ) +var ( + dubboPropertiesPath = "/dubbo/dubbo.properties" +) + func initZkData(t *testing.T) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Event) { ts, client, event, err := NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) @@ -58,10 +62,10 @@ func initZkData(t *testing.T) (*zk.TestCluster, *ZookeeperClient, <-chan zk.Even dubbo.service.com.ikurento.user.UserProvider.cluster=failover ` - err = client.Create("/dubbo/dubbo.properties") + err = client.Create(dubboPropertiesPath) assert.NoError(t, err) - _, err = client.Conn.Set("/dubbo/dubbo.properties", []byte(data), 0) + _, err = client.Conn.Set(dubboPropertiesPath, []byte(data), 0) assert.NoError(t, err) return ts, client, event @@ -99,7 +103,7 @@ func TestListener(t *testing.T) { dataListener := &mockDataListener{client: client, changedData: changedData, wait: &wait} listener.ListenServiceEvent(nil, "/dubbo", dataListener) time.Sleep(1 * time.Second) - _, err := client.Conn.Set("/dubbo/dubbo.properties", []byte(changedData), 1) + _, err := client.Conn.Set(dubboPropertiesPath, []byte(changedData), 1) assert.NoError(t, err) wait.Wait() assert.Equal(t, changedData, dataListener.eventList[1].Content)