diff --git a/.gitignore b/.gitignore
index 568e9f24541dd6f02dd8670436fd48db481b7f21..fabff68b874df4c2a7de15ce91798e9bb963b358 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,15 +20,17 @@ 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*
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/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)
若你正在使用 [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/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/common/constant/default.go b/common/constant/default.go
index c69989b4fbc3e95cb42c7f5e403989b9cff9215b..c1c404e089ea90899d2b599b01cd5980c3e92ab1 100644
--- a/common/constant/default.go
+++ b/common/constant/default.go
@@ -76,3 +76,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/key.go b/common/constant/key.go
index 8123d66557157a0cd3a267df36ee8a9d56df1aab..cd23dd0f1ad3e000fe54c251d9563bcf12ba86c7 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -22,10 +22,12 @@ const (
)
const (
+ PORT_KEY = "port"
GROUP_KEY = "group"
VERSION_KEY = "version"
INTERFACE_KEY = "interface"
PATH_KEY = "path"
+ PROTOCOL_KEY = "protocol"
SERVICE_KEY = "service"
METHODS_KEY = "methods"
TIMEOUT_KEY = "timeout"
@@ -40,6 +42,7 @@ const (
TOKEN_KEY = "token"
LOCAL_ADDR = "local-addr"
REMOTE_ADDR = "remote-addr"
+ PATH_SEPARATOR = "/"
DUBBO_KEY = "dubbo"
RELEASE_KEY = "release"
ANYHOST_KEY = "anyhost"
@@ -77,6 +80,11 @@ const (
EXECUTE_REJECTED_EXECUTION_HANDLER_KEY = "execute.limit.rejected.handler"
PROVIDER_SHUTDOWN_FILTER = "pshutdown"
CONSUMER_SHUTDOWN_FILTER = "cshutdown"
+ PID_KEY = "pid"
+ SYNC_REPORT_KEY = "sync.report"
+ RETRY_PERIOD_KEY = "retry.period"
+ RETRY_TIMES_KEY = "retry.times"
+ CYCLE_REPORT_KEY = "cycle.report"
)
const (
@@ -155,6 +163,14 @@ const (
NACOS_USERNAME = "username"
)
+const (
+ ZOOKEEPER_KEY = "zookeeper"
+)
+
+const (
+ ETCDV3_KEY = "etcdv3"
+)
+
const (
TRACING_REMOTE_SPAN_CTX = "tracing.remote.span.ctx"
)
@@ -230,7 +246,6 @@ const (
KEY_SEPARATOR = ":"
DEFAULT_PATH_TAG = "metadata"
KEY_REVISON_PREFIX = "revision"
- PATH_SEPARATOR = "/"
// metadata service
METADATA_SERVICE_NAME = "org.apache.dubbo.metadata.MetadataService"
@@ -259,7 +274,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/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 d5b9792f537fd92e60beff72e6c2fa294e64b483..807d0ed5eff4ecb70d3adeb8524b841d0ec92a58 100644
--- a/common/url.go
+++ b/common/url.go
@@ -36,6 +36,7 @@ import (
import (
"github.com/apache/dubbo-go/common/constant"
+ "github.com/apache/dubbo-go/common/logger"
)
// ///////////////////////////////
@@ -178,10 +179,12 @@ func WithToken(token string) option {
if len(token) > 0 {
value := token
if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" {
- UUID, err := uuid.NewV4()
- if err == nil {
- value = UUID.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)
}
@@ -654,3 +657,22 @@ func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []f
}
return methodConfigMergeFcn
}
+
+// 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 16e841792c7bffaa6f847d73c336c6a43431bc49..ef99664fa298c28365ed7acc54d0c18a88c9b5c2 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 fd749a934b8344a5ed00343f1b18f10bc41fd820..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 {
@@ -263,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, "|") {
@@ -278,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)
@@ -313,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())
diff --git a/config/base_config_test.go b/config/base_config_test.go
index ca2875fb95e9bdabcc430edc15bd4a56c440bc45..15b468753ddfd99e77b3d99c0994c0599c649793 100644
--- a/config/base_config_test.go
+++ b/config/base_config_test.go
@@ -116,10 +116,12 @@ func TestRefresh(t *testing.T) {
config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
father := &ConsumerConfig{
- Check: &[]bool{true}[0],
- ApplicationConfig: baseAppConfig,
- Registries: baseRegistries,
- References: baseMockRef,
+ Check: &[]bool{true}[0],
+ BaseConfig: BaseConfig{
+ ApplicationConfig: baseAppConfig,
+ },
+ Registries: baseRegistries,
+ References: baseMockRef,
ShutdownConfig: &ShutdownConfig{
Timeout: "12s",
StepTimeout: "2s",
@@ -148,10 +150,12 @@ func TestAppExternalRefresh(t *testing.T) {
mockMap["dubbo.consumer.check"] = "true"
config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
father := &ConsumerConfig{
- Check: &[]bool{true}[0],
- ApplicationConfig: baseAppConfig,
- Registries: baseRegistries,
- References: baseMockRef,
+ Check: &[]bool{true}[0],
+ BaseConfig: BaseConfig{
+ ApplicationConfig: baseAppConfig,
+ },
+ Registries: baseRegistries,
+ References: baseMockRef,
}
c.SetFatherConfig(father)
@@ -174,10 +178,12 @@ func TestAppExternalWithoutIDRefresh(t *testing.T) {
mockMap["dubbo.consumer.check"] = "true"
config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
father := &ConsumerConfig{
- Check: &[]bool{true}[0],
- ApplicationConfig: baseAppConfig,
- Registries: baseRegistries,
- References: baseMockRef,
+ Check: &[]bool{true}[0],
+ BaseConfig: BaseConfig{
+ ApplicationConfig: baseAppConfig,
+ },
+ Registries: baseRegistries,
+ References: baseMockRef,
}
c.SetFatherConfig(father)
@@ -202,11 +208,13 @@ func TestRefreshSingleRegistry(t *testing.T) {
config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
father := &ConsumerConfig{
- Check: &[]bool{true}[0],
- ApplicationConfig: baseAppConfig,
- Registries: map[string]*RegistryConfig{},
- Registry: &RegistryConfig{},
- References: baseMockRef,
+ Check: &[]bool{true}[0],
+ BaseConfig: BaseConfig{
+ ApplicationConfig: baseAppConfig,
+ },
+ Registries: map[string]*RegistryConfig{},
+ Registry: &RegistryConfig{},
+ References: baseMockRef,
}
c.SetFatherConfig(father)
@@ -231,8 +239,10 @@ func TestRefreshProvider(t *testing.T) {
config.GetEnvInstance().UpdateExternalConfigMap(mockMap)
father := &ProviderConfig{
- ApplicationConfig: baseAppConfig,
- Registries: baseRegistries,
+ BaseConfig: BaseConfig{
+ ApplicationConfig: baseAppConfig,
+ },
+ Registries: baseRegistries,
Services: map[string]*ServiceConfig{
"MockService": {
InterfaceName: "com.MockService",
@@ -299,7 +309,7 @@ func TestInitializeStruct(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 f8f83533d4796cc6df3e2d4c2d77f42be56e5933..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
}
}
@@ -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 a21a4998aae9eb6e0bd0632c7248cc39e7bbd9fa..2cbf526a70b52f184f500bad98edb01b97d239ec 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 (
@@ -93,7 +94,7 @@ func TestLoad(t *testing.T) {
func TestLoadWithSingleReg(t *testing.T) {
doInitConsumerWithSingleRegistry()
- doInitProviderWithSingleRegistry()
+ mockInitProviderWithSingleRegistry()
ms := &MockService{}
SetConsumerService(ms)
@@ -236,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/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_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 b80a77657568d0821fddca2fc16a0ccc90d7f96e..1c585ee79d58826b227df52574b3403639856306 100644
--- a/config/metadata_report_config_test.go
+++ b/config/metadata_report_config_test.go
@@ -23,13 +23,16 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestMetadataReportConfigToUrl(t *testing.T) {
- metadataReportConfig := MetadataReportConfig{
- Protocol: "mock",
+func TestMetadataReportConfig_ToUrl(t *testing.T) {
+ 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/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..748b2d403fe315f879c817c4e0a4cf3197d807da 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())
diff --git a/config/reference_config_test.go b/config/reference_config_test.go
index e43f5aa40af84b9f15a5595ce23696b6a1bae9a4..3fbf8da44ca7d00e335260cf107e99dae3a2fa8a 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",
@@ -136,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{
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 70a344c7b8c858a25e9a556ac6f4e899b1626ab4..57fce028fa59893979fc6f50671fe8681770a2b8 100644
--- a/config/service_config.go
+++ b/config/service_config.go
@@ -73,14 +73,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 + "."
}
@@ -94,6 +98,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
}
@@ -107,6 +113,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()
@@ -125,7 +141,7 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List {
return ports
}
-// Export ...
+// Export exports the service
func (c *ServiceConfig) Export() error {
// TODO: config center start here
@@ -142,7 +158,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
@@ -178,6 +194,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
@@ -190,23 +209,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
}
@@ -275,3 +318,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 798984874a7983f4236a3e60d369b0dc9b255357..e7d55077beb56b0ccf8da833f5aeebfac07a9e3f 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": {
@@ -97,6 +66,7 @@ func doInitProvider() {
Weight: 200,
},
},
+ exported: new(atomic.Bool),
},
"MockServiceNoRightProtocol": {
InterfaceName: "com.MockService",
@@ -121,58 +91,45 @@ func doInitProvider() {
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",
@@ -190,6 +147,7 @@ func TestExport(t *testing.T) {
for i := range providerConfig.Services {
service := providerConfig.Services[i]
service.Implement(&MockService{})
+ service.Protocols = providerConfig.Protocols
service.Export()
}
providerConfig = nil
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/nacos/client_test.go b/config_center/nacos/client_test.go
index 856522587d97f8be7a51c59ae36721ad88db04e8..01319f362b956a0a15be8d5d4dde2a8b2be57c89 100644
--- a/config_center/nacos/client_test.go
+++ b/config_center/nacos/client_test.go
@@ -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/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_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_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_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_test.go b/filter/filter_impl/generic_service_filter_test.go
index 2a911659f068b53836b87af5caf5773d8ac5f119..f0bdb7fb334f31de27d32d79124e4cb8c03f45a0 100644
--- a/filter/filter_impl/generic_service_filter_test.go
+++ b/filter/filter_impl/generic_service_filter_test.go
@@ -77,7 +77,7 @@ 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_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/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_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_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..15dc32e7ec1d30936b37ab2350c899e5c30d2ab7 100644
--- a/filter/filter_impl/tracing_filter_test.go
+++ b/filter/filter_impl/tracing_filter_test.go
@@ -36,7 +36,7 @@ import (
"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 76fb862b00bd757080c7effd2b7ae0ba813b39a1..e82a04b279ef16297d029b2a3993b5327b23c804 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,9 @@ 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/apache/dubbo-go-hessian2 v1.6.1-0.20200623062814-707fde850279
+ github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5 // indirect
+ github.com/apache/dubbo-go-hessian2 v1.6.1
+ github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
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
@@ -11,9 +13,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/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
@@ -25,13 +28,14 @@ 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/hashicorp/vault v0.10.3
github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b // indirect
github.com/magiconair/properties v1.8.1
github.com/mitchellh/mapstructure v1.1.2
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
- github.com/nacos-group/nacos-sdk-go v0.3.2
+ 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
diff --git a/go.sum b/go.sum
index d426cc60b553a56c195c831834fff7ce8afab88b..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,10 +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/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=
@@ -51,6 +58,7 @@ github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f h1:/8NcnxL6
github.com/asaskevich/govalidator v0.0.0-20180319081651-7d2e70ef918f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go v1.15.24 h1:xLAdTA/ore6xdPAljzZRed7IGqQgC+nY+ERS5vaj4Ro=
github.com/aws/aws-sdk-go v1.15.24/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
+github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -105,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=
@@ -134,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=
@@ -149,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=
@@ -203,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,6 +404,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
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=
@@ -395,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=
@@ -452,7 +474,10 @@ 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/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=
@@ -506,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=
@@ -523,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=
@@ -540,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=
@@ -548,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=
@@ -581,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=
@@ -626,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!The 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..4689c6660b7da78609501c5e98f0dd309e4bce7f
--- /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 (
+ "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"
+ _ "github.com/apache/dubbo-go/protocol/dubbo"
+)
+
+func TestConfigurableExporter(t *testing.T) {
+ dubbo.SetServerConfig(dubbo.ServerConfig{
+ SessionNumber: 700,
+ SessionTimeout: "20s",
+ GettySessionParam: dubbo.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/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..720c44a6f98e4693bb2395a538b2f5e679196647 100644
--- a/registry/nacos/service_discovery_test.go
+++ b/registry/nacos/service_discovery_test.go
@@ -18,7 +18,6 @@
package nacos
import (
- "strconv"
"testing"
)
@@ -27,22 +26,65 @@ 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) {
+ prepareData()
+ extension.SetEventDispatcher("mock", func() observer.EventDispatcher {
+ return &dispatcher.MockEventDispatcher{}
+ })
+
+ extension.SetAndInitGlobalDispatcher("mock")
+
serviceName := "service-name"
id := "id"
host := "host"
@@ -59,7 +101,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{
@@ -112,11 +154,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..949a5822c237de413b59d35efe94f807975795cf
--- /dev/null
+++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.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 synthesizer
+
+import (
+ "github.com/apache/dubbo-go/common"
+ "github.com/apache/dubbo-go/registry"
+)
+
+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 9a5ef60993598cf93c469989d68c991657bd5fb8..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)
@@ -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/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/registry/nacos/base_registry.go b/remoting/nacos/builder.go
similarity index 64%
rename from registry/nacos/base_registry.go
rename to remoting/nacos/builder.go
index 51c1eb8ad350231d1d2901f8acef3dbde204a741..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
@@ -104,3 +91,40 @@ func getNacosConfig(url *common.URL) (map[string]interface{}, error) {
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 226ec24085ddd0cfaea0c0d6a0ba6c91a9840ad0..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.RWMutex // 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,and 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 f9b9332504f445724a54b94356771e4ad49b62f0..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
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 5188ce8c44355cf78eeb8c6078ab4f2f49e516db..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,7 +267,7 @@ 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
+ // 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()
@@ -265,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)
@@ -302,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)
@@ -318,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()
}