diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8d84e695a59e8064cf06a2f8879564efd5588d44 --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,5 @@ +notifications: + commits: commits@dubbo.apache.org + issues: notifications@dubbo.apache.org + pullrequests: notifications@dubbo.apache.org + jira_options: link label link label diff --git a/.travis.yml b/.travis.yml index 7f30febe7bbd95ffbf1c25abce997408c7681074..566c88ece05bd80175eea2d1de8fd061a279e273 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,35 @@ -language: go +dist: trusty +sudo: required +# define the dependence env +language: go os: - linux - - osx - go: - "1.13" - +services: + - docker env: - GO111MODULE=on - install: true +# define ci-stage script: + # license-check + - echo 'start license check' - go fmt ./... && [[ -z `git status -s` ]] + - sh before_validate_license.sh + - chmod u+x /tmp/tools/license/license-header-checker + - /tmp/tools/license/license-header-checker -v -a -r -i vendor /tmp/tools/license/license.txt . go && [[ -z `git status -s` ]] + # unit-test + - echo 'start unit-test' - chmod u+x before_ut.sh && ./before_ut.sh - go mod vendor && go test ./... -coverprofile=coverage.txt -covermode=atomic + # integrate-test + - chmod +x integrate_test.sh && ./integrate_test.sh after_success: - bash <(curl -s https://codecov.io/bash) notifications: - webhooks: https://oapi.dingtalk.com/robot/send?access_token=f5d6237f2c79db584e75604f7f88db1ce1673c8c0e98451217b28fde791e1d4f \ No newline at end of file + webhooks: https://oapi.dingtalk.com/robot/send?access_token=f5d6237f2c79db584e75604f7f88db1ce1673c8c0e98451217b28fde791e1d4f diff --git a/CHANGE.md b/CHANGE.md index ad8bc594cd92ea0c72a284a262cc0cb3630cbeac..00b074d284971d779c84792951262879098fc18b 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -8,6 +8,7 @@ - [Context support](https://github.com/apache/dubbo-go/pull/330) - [Opentracing & transfer context end to end for jsonrpc protocol](https://github.com/apache/dubbo-go/pull/335) - [Opentracing & transfer context end to end for dubbo protocol](https://github.com/apache/dubbo-go/pull/344) +- [Grpc tracing for client and server](https://github.com/apache/dubbo-go/pull/397) - [Nacos config center](https://github.com/apache/dubbo-go/pull/357) - [Prometheus support](https://github.com/apache/dubbo-go/pull/342) - [Support sign and auth for request](https://github.com/apache/dubbo-go/pull/323) diff --git a/README.md b/README.md index e43b1e9aed6b82b2e367edf43728aee24a521d68..9bade617c8b05ec52c2018cf231ae036a7ae91d3 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Apache License, Version 2.0 ## Release note ## +[v1.4.0 - Mar 17, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.4.0) + [v1.3.0 - Mar 1, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.3.0) [v1.2.0 - Nov 15, 2019](https://github.com/apache/dubbo-go/releases/tag/v1.2.0) @@ -28,7 +30,7 @@ Apache License, Version 2.0 Both extension module and layered project architecture is according to Apache Dubbo (including protocol layer, registry layer, cluster layer, config layer and so on), the advantage of this arch is as following: you can implement these layered interfaces in your own way, override the default implementation of dubbo-go by calling 'extension.SetXXX' of extension, complete your special needs without modifying the source code. At the same time, you are welcome to contribute implementation of useful extension to the community. - + If you wanna know more about dubbo-go, please visit this reference [Project Architeture design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) @@ -56,6 +58,7 @@ Finished List: - Router * [Condition router](https://github.com/apache/dubbo-go/pull/294) + * [Health check router](https://github.com/apache/dubbo-go/pull/389) - Registry * ZooKeeper @@ -91,13 +94,21 @@ Finished List: * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237) * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246) * [GenericServiceFilter](https://github.com/apache/dubbo-go/pull/291) + * [Auth/Sign](https://github.com/apache/dubbo-go/pull/323) + * [Metrics filter](https://github.com/apache/dubbo-go/pull/342) + * [Tracing filter](https://github.com/apache/dubbo-go/pull/335) - Invoke * [generic invoke](https://github.com/apache/dubbo-go/pull/122) - Monitor * Opentracing API - * Prometheus + * [Prometheus](https://github.com/apache/dubbo-go/pull/342) + +- Tracing + * [For jsonrpc](https://github.com/apache/dubbo-go/pull/335) + * [For dubbo](https://github.com/apache/dubbo-go/pull/344) + * [For grpc](https://github.com/apache/dubbo-go/pull/397) - Others: * start check diff --git a/README_CN.md b/README_CN.md index e70e6786313d6a012f377f2e349880740b30c50b..180759f36663a587ee02232e229ae7c3ebbb06c1 100644 --- a/README_CN.md +++ b/README_CN.md @@ -15,6 +15,8 @@ Apache License, Version 2.0 ## 鍙戝竷鏃ュ織 ## +[v1.4.0 - 2020骞�3鏈�17鏃(https://github.com/apache/dubbo-go/releases/tag/v1.4.0) + [v1.3.0 - 2020骞�3鏈�1鏃(https://github.com/apache/dubbo-go/releases/tag/v1.3.0) [v1.2.0 - 2019骞�11鏈�15鏃(https://github.com/apache/dubbo-go/releases/tag/v1.2.0) @@ -27,7 +29,7 @@ Apache License, Version 2.0 鍩轰簬dubbo鐨別xtension妯″潡鍜屽垎灞傜殑浠g爜璁捐(鍖呮嫭 protocol layer, registry layer, cluster layer, config 绛夌瓑)銆傛垜浠殑鐩爣鏄細浣犲彲浠ュ杩欎簺鍒嗗眰鎺ュ彛杩涜鏂扮殑瀹炵幇锛屽苟閫氳繃璋冪敤 extension 妯″潡鐨勨€� extension.SetXXX 鈥濇柟娉曟潵瑕嗙洊 dubbo-go [鍚� go-for-apache-dubbo ]鐨勯粯璁ゅ疄鐜帮紝浠ュ畬鎴愯嚜宸辩殑鐗规畩闇€姹傝€屾棤闇€淇敼婧愪唬鐮併€傚悓鏃讹紝娆㈣繋浣犱负绀惧尯璐$尞鏈夌敤鐨勬嫇灞曞疄鐜般€� - + 鍏充簬璇︾粏璁捐璇烽槄璇� [code layered design](https://github.com/apache/dubbo-go/wiki/dubbo-go-V1.0-design) @@ -55,6 +57,7 @@ Apache License, Version 2.0 - 璺敱鍣� * [Condition router](https://github.com/apache/dubbo-go/pull/294) + * [Health check router](https://github.com/apache/dubbo-go/pull/389) - 娉ㄥ唽涓績 * ZooKeeper @@ -89,13 +92,22 @@ Apache License, Version 2.0 * [AccessLogFilter](https://github.com/apache/dubbo-go/pull/214) * [TpsLimitFilter](https://github.com/apache/dubbo-go/pull/237) * [ExecuteLimitFilter](https://github.com/apache/dubbo-go/pull/246) + * [Auth/Sign](https://github.com/apache/dubbo-go/pull/323) + * [Metrics filter](https://github.com/apache/dubbo-go/pull/342) + * [Tracing filter](https://github.com/apache/dubbo-go/pull/335) - 璋冪敤 * [娉涘寲璋冪敤](https://github.com/apache/dubbo-go/pull/122) - 鐩戞帶 * Opentracing API - * Prometheus + * [Prometheus](https://github.com/apache/dubbo-go/pull/342) + +- Tracing + * [For jsonrpc](https://github.com/apache/dubbo-go/pull/335) + * [For dubbo](https://github.com/apache/dubbo-go/pull/344) + * [For grpc](https://github.com/apache/dubbo-go/pull/397) + - 鍏朵粬鍔熻兘鏀寔: * 鍚姩鏃舵鏌� diff --git a/before_ut.bat b/before_ut.bat index 5e1c877af229b2b30bffc8b802cc35b6aab6c80a..dc51008dadaad21af6fcb6021863ff4102b0afa2 100644 --- a/before_ut.bat +++ b/before_ut.bat @@ -20,7 +20,7 @@ set zkJarPath=remoting/zookeeper/zookeeper-4unittest/contrib/fatjar set zkJar=%zkJarPath%/%zkJarName% if not exist "%zkJar%" ( - md %zkJarPath% + md "%zkJarPath%" curl -L %remoteJarUrl% -o %zkJar% ) diff --git a/before_validate_license.sh b/before_validate_license.sh new file mode 100644 index 0000000000000000000000000000000000000000..8fa6e381c7a4cd44835d107ba9213f685f899a10 --- /dev/null +++ b/before_validate_license.sh @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +remoteLicenseCheckerPath="https://github.com/dubbogo/resources/raw/master/tools/license" +remoteLicenseCheckerName="license-header-checker" +remoteLicenseCheckerURL="${remoteLicenseCheckerPath}/${remoteLicenseCheckerName}" +remoteLicenseName="license.txt" +remoteLicenseURL="${remoteLicenseCheckerPath}/${remoteLicenseName}" + +licensePath="/tmp/tools/license" +mkdir -p ${licensePath} +wget -P "${licensePath}" ${remoteLicenseCheckerURL} +wget -P "${licensePath}" ${remoteLicenseURL} diff --git a/cluster/cluster.go b/cluster/cluster.go index 617ce5ebf0fa7b5dc7f6047caacec9865aa6960f..7a0df4c0a24faf45d6d3d3da8651600481f782ba 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -21,7 +21,8 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// Cluster ... +// Cluster +// Extension - Cluster type Cluster interface { Join(Directory) protocol.Invoker } diff --git a/cluster/cluster_impl/available_cluster.go b/cluster/cluster_impl/available_cluster.go index 2ad140b93e15b97d1517119b07b1080a68a0503f..b70a97fad2de1b267ac1c6a5f0672ff445fadcc3 100644 --- a/cluster/cluster_impl/available_cluster.go +++ b/cluster/cluster_impl/available_cluster.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl @@ -31,7 +31,9 @@ func init() { extension.SetCluster(available, NewAvailableCluster) } -// NewAvailableCluster ... +// NewAvailableCluster returns a cluster instance +// +// Obtain available service providers func NewAvailableCluster() cluster.Cluster { return &availableCluster{} } diff --git a/cluster/cluster_impl/available_cluster_invoker.go b/cluster/cluster_impl/available_cluster_invoker.go index 6f6d2dffbbbf2f6c758097b11713ae0c1b6bd387..39a892379d3871565ec977aac874bb6509515ee2 100644 --- a/cluster/cluster_impl/available_cluster_invoker.go +++ b/cluster/cluster_impl/available_cluster_invoker.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl @@ -35,7 +35,7 @@ type availableClusterInvoker struct { baseClusterInvoker } -// NewAvailableClusterInvoker ... +// NewAvailableClusterInvoker returns a cluster invoker instance func NewAvailableClusterInvoker(directory cluster.Directory) protocol.Invoker { return &availableClusterInvoker{ baseClusterInvoker: newBaseClusterInvoker(directory), diff --git a/cluster/cluster_impl/available_cluster_invoker_test.go b/cluster/cluster_impl/available_cluster_invoker_test.go index dc0666d5afa633c86fdfaa48b93a334c19bb0248..063100020ad36192a051d1e736af7264cd8df42d 100644 --- a/cluster/cluster_impl/available_cluster_invoker_test.go +++ b/cluster/cluster_impl/available_cluster_invoker_test.go @@ -1,24 +1,25 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl import ( "context" + "fmt" "strings" "testing" ) @@ -32,6 +33,7 @@ import ( "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/cluster/loadbalance" "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/protocol/invocation" @@ -39,10 +41,11 @@ import ( ) var ( - availableUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + availableUrl, _ = common.NewURL(fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", + constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) ) -func registerAvailable(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { +func registerAvailable(invoker *mock.MockInvoker) protocol.Invoker { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) availableCluster := NewAvailableCluster() @@ -60,7 +63,7 @@ func TestAvailableClusterInvokerSuccess(t *testing.T) { defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerAvailable(t, invoker) + clusterInvoker := registerAvailable(invoker) mockResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} invoker.EXPECT().IsAvailable().Return(true) @@ -76,7 +79,7 @@ func TestAvailableClusterInvokerNoAvail(t *testing.T) { defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerAvailable(t, invoker) + clusterInvoker := registerAvailable(invoker) invoker.EXPECT().IsAvailable().Return(false) diff --git a/cluster/cluster_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index 12799994125c4bf5d968dfc811cda374effbf85c..bbdfa715d7cdc461689e60a5a41171ad5c9770e1 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -87,8 +87,11 @@ func (invoker *baseClusterInvoker) checkWhetherDestroyed() error { } func (invoker *baseClusterInvoker) doSelect(lb cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, invoked []protocol.Invoker) protocol.Invoker { - var selectedInvoker protocol.Invoker + if len(invokers) <= 0 { + return selectedInvoker + } + url := invokers[0].GetUrl() sticky := url.GetParamBool(constant.STICKY_KEY, false) //Get the service method sticky config if have @@ -98,19 +101,17 @@ func (invoker *baseClusterInvoker) doSelect(lb cluster.LoadBalance, invocation p invoker.stickyInvoker = nil } - if sticky && invoker.stickyInvoker != nil && (invoked == nil || !isInvoked(invoker.stickyInvoker, invoked)) { - if invoker.availablecheck && invoker.stickyInvoker.IsAvailable() { - return invoker.stickyInvoker - } + if sticky && invoker.availablecheck && + invoker.stickyInvoker != nil && invoker.stickyInvoker.IsAvailable() && + (invoked == nil || !isInvoked(invoker.stickyInvoker, invoked)) { + return invoker.stickyInvoker } selectedInvoker = invoker.doSelectInvoker(lb, invocation, invokers, invoked) - if sticky { invoker.stickyInvoker = selectedInvoker } return selectedInvoker - } func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, invoked []protocol.Invoker) protocol.Invoker { diff --git a/cluster/cluster_impl/base_cluster_invoker_test.go b/cluster/cluster_impl/base_cluster_invoker_test.go index d074697b85a3cf5b770de90da4847043d98c9df1..695ffcddbbce5a1c65f806b4561670d726588aaa 100644 --- a/cluster/cluster_impl/base_cluster_invoker_test.go +++ b/cluster/cluster_impl/base_cluster_invoker_test.go @@ -33,7 +33,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func Test_StickyNormal(t *testing.T) { +func TestStickyNormal(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) @@ -43,12 +43,15 @@ func Test_StickyNormal(t *testing.T) { base := &baseClusterInvoker{} base.availablecheck = true invoked := []protocol.Invoker{} - result := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) - result1 := base.doSelect(loadbalance.NewRandomLoadBalance(), invocation.NewRPCInvocation("getUser", nil, nil), invokers, invoked) + + tmpRandomBalance := loadbalance.NewRandomLoadBalance() + tmpInvocation := invocation.NewRPCInvocation("getUser", nil, nil) + result := base.doSelect(tmpRandomBalance, tmpInvocation, invokers, invoked) + result1 := base.doSelect(tmpRandomBalance, tmpInvocation, invokers, invoked) assert.Equal(t, result, result1) } -func Test_StickyNormalWhenError(t *testing.T) { +func TestStickyNormalWhenError(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) diff --git a/cluster/cluster_impl/broadcast_cluster.go b/cluster/cluster_impl/broadcast_cluster.go index 9b27a4ce37bc73e42b55e4e20deb9593fd837444..ba454af6a8553f31b72b1d30ef5f44ec7a8278d2 100644 --- a/cluster/cluster_impl/broadcast_cluster.go +++ b/cluster/cluster_impl/broadcast_cluster.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl @@ -31,7 +31,10 @@ func init() { extension.SetCluster(broadcast, NewBroadcastCluster) } -// NewBroadcastCluster ... +// NewBroadcastCluster returns a broadcast cluster instance. +// +// Calling all providers' broadcast one by one. All errors will be reported. +// It is usually used to notify all providers to update local resource information such as caches or logs. func NewBroadcastCluster() cluster.Cluster { return &broadcastCluster{} } diff --git a/cluster/cluster_impl/broadcast_cluster_invoker.go b/cluster/cluster_impl/broadcast_cluster_invoker.go index 1b49e9a115252d4eca94bedd557ebcc21fee4cc7..3a97d3d9b499c011ac90cb88b6692565388411a7 100644 --- a/cluster/cluster_impl/broadcast_cluster_invoker.go +++ b/cluster/cluster_impl/broadcast_cluster_invoker.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl diff --git a/cluster/cluster_impl/broadcast_cluster_invoker_test.go b/cluster/cluster_impl/broadcast_cluster_invoker_test.go index 1de5270265a79b4d1d62317730bd06927d939acd..08d0002ee79b2f3fda5a50ce90747c0aaad91932 100644 --- a/cluster/cluster_impl/broadcast_cluster_invoker_test.go +++ b/cluster/cluster_impl/broadcast_cluster_invoker_test.go @@ -1,25 +1,26 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl import ( "context" "errors" + "fmt" "testing" ) @@ -32,6 +33,7 @@ import ( "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/cluster/loadbalance" "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/protocol/invocation" @@ -39,10 +41,11 @@ import ( ) var ( - broadcastUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + broadcastUrl, _ = common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) ) -func registerBroadcast(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol.Invoker { +func registerBroadcast(mockInvokers ...*mock.MockInvoker) protocol.Invoker { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) invokers := []protocol.Invoker{} @@ -59,7 +62,7 @@ func registerBroadcast(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol return clusterInvoker } -func Test_BroadcastInvokeSuccess(t *testing.T) { +func TestBroadcastInvokeSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -72,13 +75,13 @@ func Test_BroadcastInvokeSuccess(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) } - clusterInvoker := registerBroadcast(t, invokers...) + clusterInvoker := registerBroadcast(invokers...) result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) } -func Test_BroadcastInvokeFailed(t *testing.T) { +func TestBroadcastInvokeFailed(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -102,7 +105,7 @@ func Test_BroadcastInvokeFailed(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).Return(mockResult) } - clusterInvoker := registerBroadcast(t, invokers...) + clusterInvoker := registerBroadcast(invokers...) result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockFailedResult.Err, result.Error()) diff --git a/cluster/cluster_impl/failback_cluster.go b/cluster/cluster_impl/failback_cluster.go index 76573571684c07f63609009f59ab0ac881ae1b50..432e33122c2ee599bc848ca9ab1842084da5ef68 100644 --- a/cluster/cluster_impl/failback_cluster.go +++ b/cluster/cluster_impl/failback_cluster.go @@ -31,7 +31,10 @@ func init() { extension.SetCluster(failback, NewFailbackCluster) } -// NewFailbackCluster ... +// NewFailbackCluster returns a failback cluster instance +// +// Failure automatically restored, failed to record the background request, +// regular retransmission. Usually used for message notification operations. func NewFailbackCluster() cluster.Cluster { return &failbackCluster{} } diff --git a/cluster/cluster_impl/failback_cluster_test.go b/cluster/cluster_impl/failback_cluster_test.go index 4571fccec59a2f995009f57c35b56ec5e1cb3ea6..0edb81d4285fa68ceefd96100b541ba334f95bda 100644 --- a/cluster/cluster_impl/failback_cluster_test.go +++ b/cluster/cluster_impl/failback_cluster_test.go @@ -1,24 +1,25 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl import ( "context" + "fmt" "sync" "testing" "time" @@ -34,6 +35,7 @@ import ( "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/cluster/loadbalance" "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/protocol/invocation" @@ -41,11 +43,12 @@ import ( ) var ( - failbackUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + failbackUrl, _ = common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) ) // registerFailback register failbackCluster to cluster extension. -func registerFailback(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { +func registerFailback(invoker *mock.MockInvoker) protocol.Invoker { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failbackCluster := NewFailbackCluster() @@ -60,12 +63,12 @@ func registerFailback(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker } // success firstly, failback should return origin invoke result. -func Test_FailbackSuceess(t *testing.T) { +func TestFailbackSuceess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker) invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() @@ -77,12 +80,12 @@ func Test_FailbackSuceess(t *testing.T) { } // failed firstly, success later after one retry. -func Test_FailbackRetryOneSuccess(t *testing.T) { +func TestFailbackRetryOneSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker) invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() @@ -95,7 +98,7 @@ func Test_FailbackRetryOneSuccess(t *testing.T) { wg.Add(1) now := time.Now() mockSuccResult := &protocol.RPCResult{Rest: rest{tried: 0, success: true}} - invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result { + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(protocol.Invocation) protocol.Result { delta := time.Since(now).Nanoseconds() / int64(time.Second) assert.True(t, delta >= 5) wg.Done() @@ -120,12 +123,12 @@ func Test_FailbackRetryOneSuccess(t *testing.T) { } // failed firstly, and failed again after ech retry time. -func Test_FailbackRetryFailed(t *testing.T) { +func TestFailbackRetryFailed(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker) invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() @@ -141,7 +144,7 @@ func Test_FailbackRetryFailed(t *testing.T) { // add retry call that eventually failed. for i := 0; i < retries; i++ { j := i + 1 - invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result { + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(protocol.Invocation) protocol.Result { delta := time.Since(now).Nanoseconds() / int64(time.Second) assert.True(t, delta >= int64(5*j)) wg.Done() @@ -166,12 +169,12 @@ func Test_FailbackRetryFailed(t *testing.T) { } // add 10 tasks but all failed firstly, and failed again with one retry. -func Test_FailbackRetryFailed10Times(t *testing.T) { +func TestFailbackRetryFailed10Times(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker) clusterInvoker.maxRetries = 10 invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() @@ -184,7 +187,7 @@ func Test_FailbackRetryFailed10Times(t *testing.T) { var wg sync.WaitGroup wg.Add(10) now := time.Now() - invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(invocation protocol.Invocation) protocol.Result { + invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn(func(protocol.Invocation) protocol.Result { delta := time.Since(now).Nanoseconds() / int64(time.Second) assert.True(t, delta >= 5) wg.Done() @@ -208,12 +211,12 @@ func Test_FailbackRetryFailed10Times(t *testing.T) { assert.Equal(t, int64(0), clusterInvoker.taskList.Len()) } -func Test_FailbackOutOfLimit(t *testing.T) { +func TestFailbackOutOfLimit(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailback(t, invoker).(*failbackClusterInvoker) + clusterInvoker := registerFailback(invoker).(*failbackClusterInvoker) clusterInvoker.failbackTasks = 1 invoker.EXPECT().GetUrl().Return(failbackUrl).AnyTimes() diff --git a/cluster/cluster_impl/failfast_cluster.go b/cluster/cluster_impl/failfast_cluster.go index e0b80ded041cd30b379857ff00d307811e53765d..ac9ec6b821c1d0333c73fae56169d5bc8256ec5b 100644 --- a/cluster/cluster_impl/failfast_cluster.go +++ b/cluster/cluster_impl/failfast_cluster.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl @@ -31,7 +31,10 @@ func init() { extension.SetCluster(failfast, NewFailFastCluster) } -// NewFailFastCluster ... +// NewFailFastCluster returns a failfast cluster instance. +// +// Fast failure, only made a call, failure immediately error. Usually used for non-idempotent write operations, +// such as adding records. func NewFailFastCluster() cluster.Cluster { return &failfastCluster{} } diff --git a/cluster/cluster_impl/failfast_cluster_invoker.go b/cluster/cluster_impl/failfast_cluster_invoker.go index 49e7c7689f5a19a36154e092a6a83cc39da604ba..3b4dc9b721720948cf635f57191d1e6bce023a1e 100644 --- a/cluster/cluster_impl/failfast_cluster_invoker.go +++ b/cluster/cluster_impl/failfast_cluster_invoker.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl diff --git a/cluster/cluster_impl/failfast_cluster_test.go b/cluster/cluster_impl/failfast_cluster_test.go index 38e258199e05c396e22118337bbdb3ae2b955bd0..77e8e9c5da73bfc8bcf08dbd90351bfd23d7e651 100644 --- a/cluster/cluster_impl/failfast_cluster_test.go +++ b/cluster/cluster_impl/failfast_cluster_test.go @@ -1,24 +1,25 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl import ( "context" + "fmt" "testing" ) @@ -32,6 +33,7 @@ import ( "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/cluster/loadbalance" "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/protocol/invocation" @@ -39,11 +41,12 @@ import ( ) var ( - failfastUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + failfastUrl, _ = common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) ) // registerFailfast register failfastCluster to cluster extension. -func registerFailfast(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { +func registerFailfast(invoker *mock.MockInvoker) protocol.Invoker { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failfastCluster := NewFailFastCluster() @@ -57,12 +60,12 @@ func registerFailfast(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker return clusterInvoker } -func Test_FailfastInvokeSuccess(t *testing.T) { +func TestFailfastInvokeSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailfast(t, invoker) + clusterInvoker := registerFailfast(invoker) invoker.EXPECT().GetUrl().Return(failfastUrl).AnyTimes() @@ -77,12 +80,12 @@ func Test_FailfastInvokeSuccess(t *testing.T) { assert.Equal(t, 0, res.tried) } -func Test_FailfastInvokeFail(t *testing.T) { +func TestFailfastInvokeFail(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailfast(t, invoker) + clusterInvoker := registerFailfast(invoker) invoker.EXPECT().GetUrl().Return(failfastUrl).AnyTimes() diff --git a/cluster/cluster_impl/failover_cluster.go b/cluster/cluster_impl/failover_cluster.go index b16be3bafd43c7de8e2fadd109a73a3ea710e225..d30a743e034dafabad87381cdaa356e7603b74d1 100644 --- a/cluster/cluster_impl/failover_cluster.go +++ b/cluster/cluster_impl/failover_cluster.go @@ -31,7 +31,11 @@ func init() { extension.SetCluster(name, NewFailoverCluster) } -// NewFailoverCluster ... +// NewFailoverCluster returns a failover cluster instance +// +// Failure automatically switch, when there is a failure, +// retry the other server (default). Usually used for read operations, +// but retries can result in longer delays. func NewFailoverCluster() cluster.Cluster { return &failoverCluster{} } diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go index 6178a05a1226ba629d2456ad6886b02a26288e45..66adabd1043d6e5d770704774dda22ba9e6faebe 100644 --- a/cluster/cluster_impl/failover_cluster_invoker.go +++ b/cluster/cluster_impl/failover_cluster_invoker.go @@ -45,52 +45,35 @@ func newFailoverClusterInvoker(directory cluster.Directory) protocol.Invoker { } func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { + var ( + result protocol.Result + invoked []protocol.Invoker + providers []string + ) invokers := invoker.directory.List(invocation) - err := invoker.checkInvokers(invokers, invocation) - - if err != nil { + if err := invoker.checkInvokers(invokers, invocation); err != nil { return &protocol.RPCResult{Err: err} } - loadbalance := getLoadBalance(invokers[0], invocation) - methodName := invocation.MethodName() - url := invokers[0].GetUrl() - - //get reties - retriesConfig := url.GetParam(constant.RETRIES_KEY, constant.DEFAULT_RETRIES) + retries := getRetries(invokers, methodName) + loadBalance := getLoadBalance(invokers[0], invocation) - //Get the service method loadbalance config if have - if v := url.GetMethodParam(methodName, constant.RETRIES_KEY, ""); len(v) != 0 { - retriesConfig = v - } - retries, err := strconv.Atoi(retriesConfig) - if err != nil || retries < 0 { - logger.Error("Your retries config is invalid,pls do a check. And will use the default retries configuration instead.") - retries = constant.DEFAULT_RETRIES_INT - } - invoked := []protocol.Invoker{} - providers := []string{} - var result protocol.Result - if retries > len(invokers) { - retries = len(invokers) - } for i := 0; i <= retries; i++ { //Reselect before retry to avoid a change of candidate `invokers`. //NOTE: if `invokers` changed, then `invoked` also lose accuracy. if i > 0 { - err := invoker.checkWhetherDestroyed() - if err != nil { + if err := invoker.checkWhetherDestroyed(); err != nil { return &protocol.RPCResult{Err: err} } + invokers = invoker.directory.List(invocation) - err = invoker.checkInvokers(invokers, invocation) - if err != nil { + if err := invoker.checkInvokers(invokers, invocation); err != nil { return &protocol.RPCResult{Err: err} } } - ivk := invoker.doSelect(loadbalance, invocation, invokers, invoked) + ivk := invoker.doSelect(loadBalance, invocation, invokers, invoked) if ivk == nil { continue } @@ -100,13 +83,40 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr if result.Error() != nil { providers = append(providers, ivk.GetUrl().Key()) continue - } else { - return result } + return result } + ip, _ := gxnet.GetLocalIP() - return &protocol.RPCResult{Err: perrors.Errorf("Failed to invoke the method %v in the service %v. Tried %v times of "+ - "the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. Last error is %v.", - methodName, invoker.GetUrl().Service(), retries, providers, len(providers), len(invokers), invoker.directory.GetUrl(), ip, constant.Version, result.Error().Error(), - )} + invokerSvc := invoker.GetUrl().Service() + invokerUrl := invoker.directory.GetUrl() + return &protocol.RPCResult{ + Err: perrors.Errorf("Failed to invoke the method %v in the service %v. Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. Last error is %v.", + methodName, invokerSvc, retries, providers, len(providers), len(invokers), invokerUrl, ip, constant.Version, result.Error().Error(), + )} +} + +func getRetries(invokers []protocol.Invoker, methodName string) int { + if len(invokers) <= 0 { + return constant.DEFAULT_RETRIES_INT + } + + url := invokers[0].GetUrl() + //get reties + retriesConfig := url.GetParam(constant.RETRIES_KEY, constant.DEFAULT_RETRIES) + //Get the service method loadbalance config if have + if v := url.GetMethodParam(methodName, constant.RETRIES_KEY, ""); len(v) != 0 { + retriesConfig = v + } + + retries, err := strconv.Atoi(retriesConfig) + if err != nil || retries < 0 { + logger.Error("Your retries config is invalid,pls do a check. And will use the default retries configuration instead.") + retries = constant.DEFAULT_RETRIES_INT + } + + if retries > len(invokers) { + retries = len(invokers) + } + return retries } diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index 1be21067a6a9045cb6ae6f84655d516fea1f844b..e05b79202cd202334db1c19421e3163ee28bac26 100644 --- a/cluster/cluster_impl/failover_cluster_test.go +++ b/cluster/cluster_impl/failover_cluster_test.go @@ -101,14 +101,14 @@ func (bi *MockInvoker) Destroy() { var count int -func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocations ...*invocation.RPCInvocation) protocol.Result { +func normalInvoke(successCount int, urlParam url.Values, invocations ...*invocation.RPCInvocation) protocol.Result { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failoverCluster := NewFailoverCluster() invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i), common.WithParams(urlParam)) - invokers = append(invokers, NewMockInvoker(url, successCount)) + newUrl, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i), common.WithParams(urlParam)) + invokers = append(invokers, NewMockInvoker(newUrl, successCount)) } staticDir := directory.NewStaticDirectory(invokers) @@ -119,40 +119,40 @@ func normalInvoke(t *testing.T, successCount int, urlParam url.Values, invocatio return clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) } -func Test_FailoverInvokeSuccess(t *testing.T) { +func TestFailoverInvokeSuccess(t *testing.T) { urlParams := url.Values{} - result := normalInvoke(t, 3, urlParams) + result := normalInvoke(3, urlParams) assert.NoError(t, result.Error()) count = 0 } -func Test_FailoverInvokeFail(t *testing.T) { +func TestFailoverInvokeFail(t *testing.T) { urlParams := url.Values{} - result := normalInvoke(t, 4, urlParams) + result := normalInvoke(4, urlParams) assert.Errorf(t, result.Error(), "error") count = 0 } -func Test_FailoverInvoke1(t *testing.T) { +func TestFailoverInvoke1(t *testing.T) { urlParams := url.Values{} urlParams.Set(constant.RETRIES_KEY, "3") - result := normalInvoke(t, 4, urlParams) + result := normalInvoke(4, urlParams) assert.NoError(t, result.Error()) count = 0 } -func Test_FailoverInvoke2(t *testing.T) { +func TestFailoverInvoke2(t *testing.T) { urlParams := url.Values{} urlParams.Set(constant.RETRIES_KEY, "2") urlParams.Set("methods.test."+constant.RETRIES_KEY, "3") ivc := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("test")) - result := normalInvoke(t, 4, urlParams, ivc) + result := normalInvoke(4, urlParams, ivc) assert.NoError(t, result.Error()) count = 0 } -func Test_FailoverDestroy(t *testing.T) { +func TestFailoverDestroy(t *testing.T) { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failoverCluster := NewFailoverCluster() @@ -170,5 +170,4 @@ func Test_FailoverDestroy(t *testing.T) { count = 0 clusterInvoker.Destroy() assert.Equal(t, false, clusterInvoker.IsAvailable()) - } diff --git a/cluster/cluster_impl/failsafe_cluster.go b/cluster/cluster_impl/failsafe_cluster.go index 177d24a585b5f72fb0667215beb8d11147cc2922..f708b7fb9108bdd17fec5dc68dc1e4249c8199d4 100644 --- a/cluster/cluster_impl/failsafe_cluster.go +++ b/cluster/cluster_impl/failsafe_cluster.go @@ -31,7 +31,10 @@ func init() { extension.SetCluster(failsafe, NewFailsafeCluster) } -// NewFailsafeCluster ... +// NewFailsafeCluster returns a failsafe cluster instance. +// +// Failure of security, anomalies, directly ignored. Usually it is +// used to write audit logs and other operations. func NewFailsafeCluster() cluster.Cluster { return &failsafeCluster{} } diff --git a/cluster/cluster_impl/failsafe_cluster_test.go b/cluster/cluster_impl/failsafe_cluster_test.go index 2e35de8da91cc78730b6380bf039f0626ca75ec0..d9a716e1ae65a84b605b4b7af1872b3a85dc9369 100644 --- a/cluster/cluster_impl/failsafe_cluster_test.go +++ b/cluster/cluster_impl/failsafe_cluster_test.go @@ -1,24 +1,25 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl import ( "context" + "fmt" "testing" ) @@ -32,6 +33,7 @@ import ( "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/cluster/loadbalance" "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/protocol/invocation" @@ -39,11 +41,12 @@ import ( ) var ( - failsafeUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + failsafeUrl, _ = common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) ) // registerFailsafe register failsafeCluster to cluster extension. -func registerFailsafe(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker { +func registerFailsafe(invoker *mock.MockInvoker) protocol.Invoker { extension.SetLoadbalance("random", loadbalance.NewRandomLoadBalance) failsafeCluster := NewFailsafeCluster() @@ -57,12 +60,12 @@ func registerFailsafe(t *testing.T, invoker *mock.MockInvoker) protocol.Invoker return clusterInvoker } -func Test_FailSafeInvokeSuccess(t *testing.T) { +func TestFailSafeInvokeSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailsafe(t, invoker) + clusterInvoker := registerFailsafe(invoker) invoker.EXPECT().GetUrl().Return(failsafeUrl).AnyTimes() @@ -76,12 +79,12 @@ func Test_FailSafeInvokeSuccess(t *testing.T) { assert.True(t, res.success) } -func Test_FailSafeInvokeFail(t *testing.T) { +func TestFailSafeInvokeFail(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() invoker := mock.NewMockInvoker(ctrl) - clusterInvoker := registerFailsafe(t, invoker) + clusterInvoker := registerFailsafe(invoker) invoker.EXPECT().GetUrl().Return(failsafeUrl).AnyTimes() diff --git a/cluster/cluster_impl/forking_cluster.go b/cluster/cluster_impl/forking_cluster.go index 6b0572b15088e86870b3d9fd911a1d0b022378be..0e6cd26882788a1f897d0d4dc8e0d4eb0a9d4218 100644 --- a/cluster/cluster_impl/forking_cluster.go +++ b/cluster/cluster_impl/forking_cluster.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl @@ -31,7 +31,10 @@ func init() { extension.SetCluster(forking, NewForkingCluster) } -// NewForkingCluster ... +// NewForkingCluster returns a forking cluster instance. +// +// Multiple servers are invoked in parallel, returning as soon as one succeeds. +// Usually it is used for real-time demanding read operations while wasting more service resources. func NewForkingCluster() cluster.Cluster { return &forkingCluster{} } diff --git a/cluster/cluster_impl/forking_cluster_invoker.go b/cluster/cluster_impl/forking_cluster_invoker.go index 058d7fefd6edf6c43e5eda4b8f2f6a9c161189e2..a5a3f2ec6605dfb843fab09dff0a53000bbc3298 100644 --- a/cluster/cluster_impl/forking_cluster_invoker.go +++ b/cluster/cluster_impl/forking_cluster_invoker.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl @@ -46,14 +46,12 @@ func newForkingClusterInvoker(directory cluster.Directory) protocol.Invoker { // Invoke ... func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { - err := invoker.checkWhetherDestroyed() - if err != nil { + if err := invoker.checkWhetherDestroyed(); err != nil { return &protocol.RPCResult{Err: err} } invokers := invoker.directory.List(invocation) - err = invoker.checkInvokers(invokers, invocation) - if err != nil { + if err := invoker.checkInvokers(invokers, invocation); err != nil { return &protocol.RPCResult{Err: err} } @@ -63,11 +61,9 @@ func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation pro if forks < 0 || forks > len(invokers) { selected = invokers } else { - selected = make([]protocol.Invoker, 0) - loadbalance := getLoadBalance(invokers[0], invocation) + loadBalance := getLoadBalance(invokers[0], invocation) for i := 0; i < forks; i++ { - ivk := invoker.doSelect(loadbalance, invocation, invokers, selected) - if ivk != nil { + if ivk := invoker.doSelect(loadBalance, invocation, invokers, selected); ivk != nil { selected = append(selected, ivk) } } @@ -77,8 +73,7 @@ func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation pro for _, ivk := range selected { go func(k protocol.Invoker) { result := k.Invoke(ctx, invocation) - err := resultQ.Put(result) - if err != nil { + if err := resultQ.Put(result); err != nil { logger.Errorf("resultQ put failed with exception: %v.\n", err) } }(ivk) @@ -99,6 +94,5 @@ func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation pro if !ok { return &protocol.RPCResult{Err: fmt.Errorf("failed to forking invoke provider %v, but not legal resp", selected)} } - return result } diff --git a/cluster/cluster_impl/forking_cluster_test.go b/cluster/cluster_impl/forking_cluster_test.go index 9797ecbd041ae2e09c3d0566f88d08b7246b900f..a2fa136d312db900f45449c92a59009c6661571c 100644 --- a/cluster/cluster_impl/forking_cluster_test.go +++ b/cluster/cluster_impl/forking_cluster_test.go @@ -1,24 +1,25 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package cluster_impl import ( "context" + "fmt" "strconv" "sync" "testing" @@ -42,10 +43,11 @@ import ( ) var ( - forkingUrl, _ = common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + forkingUrl, _ = common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) ) -func registerForking(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol.Invoker { +func registerForking(mockInvokers ...*mock.MockInvoker) protocol.Invoker { extension.SetLoadbalance(loadbalance.RoundRobin, loadbalance.NewRoundRobinLoadBalance) invokers := []protocol.Invoker{} @@ -62,7 +64,7 @@ func registerForking(t *testing.T, mockInvokers ...*mock.MockInvoker) protocol.I return clusterInvoker } -func Test_ForkingInvokeSuccess(t *testing.T) { +func TestForkingInvokeSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -79,20 +81,20 @@ func Test_ForkingInvokeSuccess(t *testing.T) { invokers = append(invokers, invoker) invoker.EXPECT().IsAvailable().Return(true).AnyTimes() invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( - func(invocation protocol.Invocation) protocol.Result { + func(protocol.Invocation) protocol.Result { wg.Done() return mockResult }) } - clusterInvoker := registerForking(t, invokers...) + clusterInvoker := registerForking(invokers...) result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) wg.Wait() } -func Test_ForkingInvokeTimeout(t *testing.T) { +func TestForkingInvokeTimeout(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -108,14 +110,14 @@ func Test_ForkingInvokeTimeout(t *testing.T) { invokers = append(invokers, invoker) invoker.EXPECT().IsAvailable().Return(true).AnyTimes() invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( - func(invocation protocol.Invocation) protocol.Result { + func(protocol.Invocation) protocol.Result { time.Sleep(2 * time.Second) wg.Done() return mockResult }) } - clusterInvoker := registerForking(t, invokers...) + clusterInvoker := registerForking(invokers...) result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.NotNil(t, result) @@ -123,7 +125,7 @@ func Test_ForkingInvokeTimeout(t *testing.T) { wg.Wait() } -func Test_ForkingInvokeHalfTimeout(t *testing.T) { +func TestForkingInvokeHalfTimeout(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -140,13 +142,13 @@ func Test_ForkingInvokeHalfTimeout(t *testing.T) { invoker.EXPECT().IsAvailable().Return(true).AnyTimes() if i == 1 { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( - func(invocation protocol.Invocation) protocol.Result { + func(protocol.Invocation) protocol.Result { wg.Done() return mockResult }) } else { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( - func(invocation protocol.Invocation) protocol.Result { + func(protocol.Invocation) protocol.Result { time.Sleep(2 * time.Second) wg.Done() return mockResult @@ -154,7 +156,7 @@ func Test_ForkingInvokeHalfTimeout(t *testing.T) { } } - clusterInvoker := registerForking(t, invokers...) + clusterInvoker := registerForking(invokers...) result := clusterInvoker.Invoke(context.Background(), &invocation.RPCInvocation{}) assert.Equal(t, mockResult, result) diff --git a/cluster/cluster_impl/mock_cluster.go b/cluster/cluster_impl/mock_cluster.go index 943c2add68281d01e320252d07b7d58e27b51283..d887cfb45b9c92c859b1396046c1c1c73d46b295 100644 --- a/cluster/cluster_impl/mock_cluster.go +++ b/cluster/cluster_impl/mock_cluster.go @@ -24,7 +24,11 @@ import ( type mockCluster struct{} -// NewMockCluster ... +// NewMockCluster returns a mock cluster instance. +// +// Mock cluster is usually used for service degradation, such as an authentication service. +// When the service provider is completely hung up, the client does not throw an exception, +// return an authorization failure through the Mock data instead. func NewMockCluster() cluster.Cluster { return &mockCluster{} } diff --git a/cluster/cluster_impl/registry_aware_cluster.go b/cluster/cluster_impl/registry_aware_cluster.go index 079b688da65b3e6f6595212ad6e93c3b6ecc6504..fcefa52862a39eece98dca8660e62d9ca144e955 100644 --- a/cluster/cluster_impl/registry_aware_cluster.go +++ b/cluster/cluster_impl/registry_aware_cluster.go @@ -29,7 +29,7 @@ func init() { extension.SetCluster("registryAware", NewRegistryAwareCluster) } -// NewRegistryAwareCluster ... +// NewRegistryAwareCluster returns a registry aware cluster instance func NewRegistryAwareCluster() cluster.Cluster { return ®istryAwareCluster{} } diff --git a/cluster/cluster_impl/registry_aware_cluster_test.go b/cluster/cluster_impl/registry_aware_cluster_test.go index 3d0dcc0159839eb0a08aed842ee084449458c645..74584b44800fce3342956f4237a63ffbbabf5544 100644 --- a/cluster/cluster_impl/registry_aware_cluster_test.go +++ b/cluster/cluster_impl/registry_aware_cluster_test.go @@ -33,7 +33,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func Test_RegAwareInvokeSuccess(t *testing.T) { +func TestRegAwareInvokeSuccess(t *testing.T) { regAwareCluster := NewRegistryAwareCluster() diff --git a/cluster/directory.go b/cluster/directory.go index 5a03b3a4490ce0b3aadece8a9ef43395f845dd12..37f0c3282935bac430d0ae676abc72d60d711c85 100644 --- a/cluster/directory.go +++ b/cluster/directory.go @@ -23,7 +23,7 @@ import ( ) // Directory -//Extension - Directory +// Extension - Directory type Directory interface { common.Node List(invocation protocol.Invocation) []protocol.Invoker diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 75d9ef26567df0fbd83f5d9f94c8548d1e8e633d..3ede3d8c0ab3691603ac69c10202155e0a331d26 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 @@ -92,7 +89,7 @@ func (dir *BaseDirectory) SetRouters(urls []*common.URL) { factory := extension.GetRouterFactory(url.Protocol) r, err := factory.NewRouter(url) if err != nil { - logger.Errorf("Create router fail. router key: %s, error: %v", routerKey, url.Service(), err) + logger.Errorf("Create router fail. router key: %s, url:%s, error: %+v", routerKey, url.Service(), err) return } routers = append(routers, r) @@ -120,14 +117,3 @@ func (dir *BaseDirectory) Destroy(doDestroy func()) { func (dir *BaseDirectory) IsAvailable() bool { return !dir.destroyed.Load() } - -// GetRouterURLSet Return router URL -func GetRouterURLSet() *gxset.HashSet { - return routerURLSet -} - -// AddRouterURLSet Add router URL -// Router URL will init in config/config_loader.go -func AddRouterURLSet(url *common.URL) { - routerURLSet.Add(url) -} diff --git a/cluster/directory/base_directory_test.go b/cluster/directory/base_directory_test.go index d5993959f1d37f343a612e2bee305461d49535d0..8b60163b79b7120829e51f69238474a127133fb4 100644 --- a/cluster/directory/base_directory_test.go +++ b/cluster/directory/base_directory_test.go @@ -34,19 +34,20 @@ import ( "github.com/apache/dubbo-go/common/constant" ) +var ( + url, _ = common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) + anyUrl, _ = common.NewURL(fmt.Sprintf("condition://%s/com.foo.BarService", constant.ANYHOST_VALUE)) +) + func TestNewBaseDirectory(t *testing.T) { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")) directory := NewBaseDirectory(&url) - assert.NotNil(t, directory) - assert.Equal(t, url, directory.GetUrl()) assert.Equal(t, &url, directory.GetDirectoryUrl()) - } func TestBuildRouterChain(t *testing.T) { - url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider")) directory := NewBaseDirectory(&url) assert.NotNil(t, directory) @@ -63,9 +64,8 @@ func TestBuildRouterChain(t *testing.T) { } func getRouteUrl(rule string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") - url.AddParam("rule", rule) - url.AddParam("force", "true") - url.AddParam(constant.ROUTER_KEY, "router") + anyUrl.AddParam("rule", rule) + anyUrl.AddParam("force", "true") + anyUrl.AddParam(constant.ROUTER_KEY, "router") return &url } diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go index 9f600fedc40cf29a40abca6c11652935f20473b4..87f51356495dbd0a956c42bf4f34022b4d21ad4d 100644 --- a/cluster/directory/static_directory.go +++ b/cluster/directory/static_directory.go @@ -61,7 +61,7 @@ func (dir *staticDirectory) IsAvailable() bool { // List List invokers func (dir *staticDirectory) List(invocation protocol.Invocation) []protocol.Invoker { l := len(dir.invokers) - invokers := make([]protocol.Invoker, l, l) + invokers := make([]protocol.Invoker, l) copy(invokers, dir.invokers) routerChain := dir.RouterChain() diff --git a/cluster/directory/static_directory_test.go b/cluster/directory/static_directory_test.go index c50c9a4063bd1a372c27e47687cbf63850f76cef..8e75a2c2535058f605c3e9bb6d6a01f9ff91032a 100644 --- a/cluster/directory/static_directory_test.go +++ b/cluster/directory/static_directory_test.go @@ -32,7 +32,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func Test_StaticDirList(t *testing.T) { +func TestStaticDirList(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) @@ -45,7 +45,7 @@ func Test_StaticDirList(t *testing.T) { assert.Len(t, list, 10) } -func Test_StaticDirDestroy(t *testing.T) { +func TestStaticDirDestroy(t *testing.T) { invokers := []protocol.Invoker{} for i := 0; i < 10; i++ { url, _ := common.NewURL(fmt.Sprintf("dubbo://192.168.1.%v:20000/com.ikurento.user.UserProvider", i)) diff --git a/cluster/loadbalance.go b/cluster/loadbalance.go index fb3641a77377eabbd692729a32e2c0c096282f18..a5b344a4952d338e0f481028b3835116f1743773 100644 --- a/cluster/loadbalance.go +++ b/cluster/loadbalance.go @@ -22,7 +22,7 @@ import ( ) // LoadBalance -//Extension - LoadBalance +// Extension - LoadBalance type LoadBalance interface { Select([]protocol.Invoker, protocol.Invocation) protocol.Invoker } diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 957c110663d6c56ada15543d372e210fa83bf74b..84fbb268c7a8ec32f007a734e2d6da56ef3c6d25 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -27,7 +27,9 @@ import ( "strconv" "strings" ) - +import ( + gxsort "github.com/dubbogo/gost/sort" +) import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/common/constant" @@ -40,7 +42,7 @@ const ( ConsistentHash = "consistenthash" // HashNodes ... HashNodes = "hash.nodes" - // HashArguments ... + // HashArguments key of hash arguments in url HashArguments = "hash.arguments" ) @@ -53,16 +55,18 @@ func init() { extension.SetLoadbalance(ConsistentHash, NewConsistentHashLoadBalance) } -// ConsistentHashLoadBalance ... +// ConsistentHashLoadBalance implementation of load balancing: using consistent hashing type ConsistentHashLoadBalance struct { } -// NewConsistentHashLoadBalance ... +// NewConsistentHashLoadBalance creates NewConsistentHashLoadBalance +// +// The same parameters of the request is always sent to the same provider. func NewConsistentHashLoadBalance() cluster.LoadBalance { return &ConsistentHashLoadBalance{} } -// Select ... +// Select gets invoker based on load balancing strategy func (lb *ConsistentHashLoadBalance) Select(invokers []protocol.Invoker, invocation protocol.Invocation) protocol.Invoker { methodName := invocation.MethodName() key := invokers[0].GetUrl().ServiceKey() + "." + methodName @@ -85,27 +89,12 @@ func (lb *ConsistentHashLoadBalance) Select(invokers []protocol.Invoker, invocat return selector.Select(invocation) } -// Uint32Slice ... -type Uint32Slice []uint32 - -func (s Uint32Slice) Len() int { - return len(s) -} - -func (s Uint32Slice) Less(i, j int) bool { - return s[i] < s[j] -} - -func (s Uint32Slice) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// ConsistentHashSelector ... +// ConsistentHashSelector implementation of Selector:get invoker based on load balancing strategy type ConsistentHashSelector struct { hashCode uint32 replicaNum int virtualInvokers map[uint32]protocol.Invoker - keys Uint32Slice + keys gxsort.Uint32Slice argumentIndex []int } @@ -141,7 +130,7 @@ func newConsistentHashSelector(invokers []protocol.Invoker, methodName string, return selector } -// Select ... +// Select gets invoker based on load balancing strategy func (c *ConsistentHashSelector) Select(invocation protocol.Invocation) protocol.Invoker { key := c.toKey(invocation.Arguments()) digest := md5.Sum([]byte(key)) diff --git a/cluster/loadbalance/least_active.go b/cluster/loadbalance/least_active.go index e7c41aac93e8d3dfcef5d49fa486483bd045f569..37ad91c3ed6b44370820a989b7af8ccaa82c48a2 100644 --- a/cluster/loadbalance/least_active.go +++ b/cluster/loadbalance/least_active.go @@ -28,7 +28,7 @@ import ( ) const ( - // LeastActive ... + // LeastActive is used to set the load balance extension LeastActive = "leastactive" ) @@ -39,7 +39,9 @@ func init() { type leastActiveLoadBalance struct { } -// NewLeastActiveLoadBalance ... +// NewLeastActiveLoadBalance returns a least active load balance. +// +// A random mechanism based on actives, actives means the number of a consumer's requests have been sent to provider but not yet got response. func NewLeastActiveLoadBalance() cluster.LoadBalance { return &leastActiveLoadBalance{} } diff --git a/cluster/loadbalance/least_active_test.go b/cluster/loadbalance/least_active_test.go index 54e57e930f17008cf6d767ef47c0e754ac85d8f7..34be17a4f311a374eefc56ba76885eef2a23645a 100644 --- a/cluster/loadbalance/least_active_test.go +++ b/cluster/loadbalance/least_active_test.go @@ -28,6 +28,7 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) @@ -37,7 +38,7 @@ func TestLeastActiveSelect(t *testing.T) { var invokers []protocol.Invoker - url, _ := common.NewURL("dubbo://192.168.1.0:20000/org.apache.demo.HelloService") + url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/org.apache.demo.HelloService", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) invokers = append(invokers, protocol.NewBaseInvoker(url)) i := loadBalance.Select(invokers, &invocation.RPCInvocation{}) assert.True(t, i.GetUrl().URLEqual(url)) diff --git a/cluster/loadbalance/random.go b/cluster/loadbalance/random.go index 56f13631b653ed070dae7def5bea97d924141209..cdde1b41fb8e986d9923681dc4ab075496ac810e 100644 --- a/cluster/loadbalance/random.go +++ b/cluster/loadbalance/random.go @@ -38,7 +38,9 @@ func init() { type randomLoadBalance struct { } -// NewRandomLoadBalance ... +// NewRandomLoadBalance returns a random load balance instance. +// +// Set random probabilities by weight, and the request will be sent to provider randomly. func NewRandomLoadBalance() cluster.LoadBalance { return &randomLoadBalance{} } diff --git a/cluster/loadbalance/random_test.go b/cluster/loadbalance/random_test.go index ff876f4aef8d229e8041594aaaa096f3ad5b1834..88392de52c93579dd4def3da2d60b415b601b21e 100644 --- a/cluster/loadbalance/random_test.go +++ b/cluster/loadbalance/random_test.go @@ -36,7 +36,7 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -func Test_RandomlbSelect(t *testing.T) { +func TestRandomlbSelect(t *testing.T) { randomlb := NewRandomLoadBalance() invokers := []protocol.Invoker{} @@ -53,7 +53,7 @@ func Test_RandomlbSelect(t *testing.T) { randomlb.Select(invokers, &invocation.RPCInvocation{}) } -func Test_RandomlbSelectWeight(t *testing.T) { +func TestRandomlbSelectWeight(t *testing.T) { randomlb := NewRandomLoadBalance() invokers := []protocol.Invoker{} @@ -84,7 +84,7 @@ func Test_RandomlbSelectWeight(t *testing.T) { }) } -func Test_RandomlbSelectWarmup(t *testing.T) { +func TestRandomlbSelectWarmup(t *testing.T) { randomlb := NewRandomLoadBalance() invokers := []protocol.Invoker{} diff --git a/cluster/loadbalance/round_robin.go b/cluster/loadbalance/round_robin.go index 4d039999677aefb1093071666a845279dc357ce9..c44b239dbcbcc744f47ca3c97128f92567e32a78 100644 --- a/cluster/loadbalance/round_robin.go +++ b/cluster/loadbalance/round_robin.go @@ -52,7 +52,9 @@ func init() { type roundRobinLoadBalance struct{} -// NewRoundRobinLoadBalance ... +// NewRoundRobinLoadBalance returns a round robin load balance +// +// Use the weight's common advisory to determine round robin ratio func NewRoundRobinLoadBalance() cluster.LoadBalance { return &roundRobinLoadBalance{} } diff --git a/cluster/loadbalance/round_robin_test.go b/cluster/loadbalance/round_robin_test.go index 1517f2a20b473af57cc23e61b988aa5a6a04de31..5354bae458605ff56ec8a9b35d36730ecdc0babb 100644 --- a/cluster/loadbalance/round_robin_test.go +++ b/cluster/loadbalance/round_robin_test.go @@ -29,6 +29,7 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" ) @@ -38,7 +39,8 @@ func TestRoundRobinSelect(t *testing.T) { var invokers []protocol.Invoker - url, _ := common.NewURL("dubbo://192.168.1.0:20000/org.apache.demo.HelloService") + url, _ := common.NewURL(fmt.Sprintf("dubbo://%s:%d/org.apache.demo.HelloService", + constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) invokers = append(invokers, protocol.NewBaseInvoker(url)) i := loadBalance.Select(invokers, &invocation.RPCInvocation{}) assert.True(t, i.GetUrl().URLEqual(url)) diff --git a/cluster/loadbalance/util.go b/cluster/loadbalance/util.go index 9f36ad9379a3a09a4a058f6179e3e537b9e105bc..b6c013852bf55ce7eb67e4fa18802a938141d283 100644 --- a/cluster/loadbalance/util.go +++ b/cluster/loadbalance/util.go @@ -26,7 +26,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// GetWeight ... +// GetWeight gets weight for load balance strategy func GetWeight(invoker protocol.Invoker, invocation protocol.Invocation) int64 { url := invoker.GetUrl() weight := url.GetMethodParamInt64(invocation.MethodName(), constant.WEIGHT_KEY, constant.DEFAULT_WEIGHT) diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go index 0cb47c4a185fe19b5f70ea4db2b80aab2f1aada5..c7a75f3d8608ecb7a95dcf33027e71b61d7f00f5 100644 --- a/cluster/router/chain/chain_test.go +++ b/cluster/router/chain/chain_test.go @@ -42,10 +42,16 @@ import ( "github.com/apache/dubbo-go/remoting/zookeeper" ) +const ( + path = "/dubbo/config/dubbo/test-condition.condition-router" + zkPrefix = "zookeeper://127.0.0.1:" + anyUrl = "condition://0.0.0.0/com.foo.BarService" +) + func TestNewRouterChain(t *testing.T) { ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + err = z.Create(path) assert.NoError(t, err) testyml := `enabled: true @@ -55,12 +61,12 @@ conditions: - => host != 172.22.3.91 ` - _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0) + _, err = z.Conn.Set(path, []byte(testyml), 0) assert.NoError(t, err) defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) @@ -92,10 +98,10 @@ func TestNewRouterChainURLNil(t *testing.T) { assert.NotNil(t, chain) } -func TestRouterChain_AddRouters(t *testing.T) { +func TestRouterChainAddRouters(t *testing.T) { ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + err = z.Create(path) assert.NoError(t, err) testyml := `enabled: true @@ -105,12 +111,12 @@ conditions: - => host != 172.22.3.91 ` - _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0) + _, err = z.Conn.Set(path, []byte(testyml), 0) assert.NoError(t, err) defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) @@ -131,12 +137,12 @@ conditions: assert.Equal(t, 3, len(chain.routers)) } -func TestRouterChain_Route(t *testing.T) { +func TestRouterChainRoute(t *testing.T) { ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) @@ -158,10 +164,10 @@ func TestRouterChain_Route(t *testing.T) { assert.Equal(t, 1, len(finalInvokers)) } -func TestRouterChain_Route_AppRouter(t *testing.T) { +func TestRouterChainRouteAppRouter(t *testing.T) { ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + err = z.Create(path) assert.NoError(t, err) testyml := `enabled: true @@ -171,12 +177,12 @@ conditions: - => host = 1.1.1.1 => host != 1.2.3.4 ` - _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testyml), 0) + _, err = z.Conn.Set(path, []byte(testyml), 0) assert.NoError(t, err) defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) @@ -200,7 +206,7 @@ func TestRouterChain_Route_NoRoute(t *testing.T) { defer ts.Stop() defer z.Close() - zkUrl, _ := common.NewURL("zookeeper://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port)) + zkUrl, _ := common.NewURL(zkPrefix + strconv.Itoa(ts.Servers[0].Port)) configuration, err := extension.GetConfigCenterFactory("zookeeper").GetDynamicConfiguration(&zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) @@ -223,7 +229,7 @@ func TestRouterChain_Route_NoRoute(t *testing.T) { } func getConditionNoRouteUrl(applicationKey string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL(anyUrl) url.AddParam("application", applicationKey) url.AddParam("force", "true") rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host != 1.2.3.4")) @@ -232,7 +238,7 @@ func getConditionNoRouteUrl(applicationKey string) *common.URL { } func getConditionRouteUrl(applicationKey string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL(anyUrl) url.AddParam("application", applicationKey) url.AddParam("force", "true") rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host = 1.2.3.4")) @@ -241,7 +247,7 @@ func getConditionRouteUrl(applicationKey string) *common.URL { } func getRouteUrl(applicationKey string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL(anyUrl) url.AddParam("application", applicationKey) url.AddParam("force", "true") return &url diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go index bd817af36c8c144295479fb07ada9411f4115bbc..f37a483e8468bc57d3ce1e73172ccf9a05bc29f0 100644 --- a/cluster/router/condition/app_router_test.go +++ b/cluster/router/condition/app_router_test.go @@ -37,6 +37,10 @@ import ( "github.com/apache/dubbo-go/remoting/zookeeper" ) +const ( + path = "/dubbo/config/dubbo/test-condition.condition-router" +) + func TestNewAppRouter(t *testing.T) { testYML := `enabled: true @@ -47,10 +51,10 @@ conditions: ` ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + err = z.Create(path) assert.NoError(t, err) - _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testYML), 0) + _, err = z.Conn.Set(path, []byte(testYML), 0) assert.NoError(t, err) defer ts.Stop() defer z.Close() @@ -93,10 +97,10 @@ conditions: ` ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + err = z.Create(path) assert.NoError(t, err) - _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testYML), 0) + _, err = z.Conn.Set(path, []byte(testYML), 0) assert.NoError(t, err) defer ts.Stop() defer z.Close() @@ -113,7 +117,7 @@ conditions: assert.Nil(t, err) assert.NotNil(t, appRouter) - rule, err := Parse(testYML) + rule, err := getRule(testYML) assert.Nil(t, err) appRouter.generateConditions(rule) @@ -130,10 +134,10 @@ conditions: ` ts, z, _, err := zookeeper.NewMockZookeeperClient("test", 15*time.Second) assert.NoError(t, err) - err = z.Create("/dubbo/config/dubbo/test-condition.condition-router") + err = z.Create(path) assert.NoError(t, err) - _, err = z.Conn.Set("/dubbo/config/dubbo/test-condition.condition-router", []byte(testYML), 0) + _, err = z.Conn.Set(path, []byte(testYML), 0) assert.NoError(t, err) defer ts.Stop() defer z.Close() diff --git a/cluster/router/condition/factory_test.go b/cluster/router/condition/factory_test.go index 99cec34096a55d3c2a967b63afdf5f6d0a77279a..a826cafb85ee1a30ac568db34e10dd2c9c9e87d0 100644 --- a/cluster/router/condition/factory_test.go +++ b/cluster/router/condition/factory_test.go @@ -38,6 +38,8 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) +const anyUrl = "condition://0.0.0.0/com.foo.BarService" + type MockInvoker struct { url common.URL available bool @@ -59,21 +61,21 @@ func (bi *MockInvoker) GetUrl() common.URL { } func getRouteUrl(rule string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL(anyUrl) url.AddParam("rule", rule) url.AddParam("force", "true") return &url } func getRouteUrlWithForce(rule, force string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL(anyUrl) url.AddParam("rule", rule) url.AddParam("force", force) return &url } func getRouteUrlWithNoForce(rule string) *common.URL { - url, _ := common.NewURL("condition://0.0.0.0/com.foo.BarService") + url, _ := common.NewURL(anyUrl) url.AddParam("rule", rule) return &url } @@ -116,7 +118,7 @@ func (bi *MockInvoker) Destroy() { bi.available = false } -func TestRoute_matchWhen(t *testing.T) { +func TestRouteMatchWhen(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4")) router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) @@ -149,7 +151,7 @@ func TestRoute_matchWhen(t *testing.T) { assert.Equal(t, true, matchWhen6) } -func TestRoute_matchFilter(t *testing.T) { +func TestRouteMatchFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() t.Logf("The local ip is %s", localIP) url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson") @@ -184,7 +186,7 @@ func TestRoute_matchFilter(t *testing.T) { } -func TestRoute_methodRoute(t *testing.T) { +func TestRouteMethodRoute(t *testing.T) { inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("getFoo"), invocation.WithParameterTypes([]reflect.Type{}), invocation.WithArguments([]interface{}{})) rule := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) router, _ := newConditionRouterFactory().NewRouter(getRouteUrl(rule)) @@ -207,7 +209,7 @@ func TestRoute_methodRoute(t *testing.T) { } -func TestRoute_ReturnFalse(t *testing.T) { +func TestRouteReturnFalse(t *testing.T) { url, _ := common.NewURL("") localIP, _ := gxnet.GetLocalIP() invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} @@ -219,7 +221,7 @@ func TestRoute_ReturnFalse(t *testing.T) { assert.Equal(t, 0, len(fileredInvokers)) } -func TestRoute_ReturnEmpty(t *testing.T) { +func TestRouteReturnEmpty(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url, _ := common.NewURL("") invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} @@ -231,7 +233,7 @@ func TestRoute_ReturnEmpty(t *testing.T) { assert.Equal(t, 0, len(fileredInvokers)) } -func TestRoute_ReturnAll(t *testing.T) { +func TestRouteReturnAll(t *testing.T) { localIP, _ := gxnet.GetLocalIP() urlString := "dubbo://" + localIP + "/com.foo.BarService" dubboURL, _ := common.NewURL(urlString) @@ -247,7 +249,7 @@ func TestRoute_ReturnAll(t *testing.T) { assert.Equal(t, invokers, fileredInvokers) } -func TestRoute_HostFilter(t *testing.T) { +func TestRouteHostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) @@ -266,7 +268,7 @@ func TestRoute_HostFilter(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRoute_Empty_HostFilter(t *testing.T) { +func TestRouteEmptyHostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) @@ -285,7 +287,7 @@ func TestRoute_Empty_HostFilter(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRoute_False_HostFilter(t *testing.T) { +func TestRouteFalseHostFilter(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) @@ -304,7 +306,7 @@ func TestRoute_False_HostFilter(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRoute_Placeholder(t *testing.T) { +func TestRoutePlaceholder(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) @@ -323,7 +325,7 @@ func TestRoute_Placeholder(t *testing.T) { assert.Equal(t, invoker3, fileredInvokers[1]) } -func TestRoute_NoForce(t *testing.T) { +func TestRouteNoForce(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) @@ -340,7 +342,7 @@ func TestRoute_NoForce(t *testing.T) { assert.Equal(t, invokers, fileredInvokers) } -func TestRoute_Force(t *testing.T) { +func TestRouteForce(t *testing.T) { localIP, _ := gxnet.GetLocalIP() url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService") url2, _ := common.NewURL(fmt.Sprintf("dubbo://%s:20880/com.foo.BarService", localIP)) diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index efeec53efc840d93c4b6906adfd19820a57b36fd..b2c876690043d18a1a9e746fee13f06c77a0de03 100644 --- a/cluster/router/condition/file.go +++ b/cluster/router/condition/file.go @@ -44,7 +44,7 @@ type FileConditionRouter struct { // NewFileConditionRouter Create file condition router instance with content ( from config file) func NewFileConditionRouter(content []byte) (*FileConditionRouter, error) { fileRouter := &FileConditionRouter{} - rule, err := Parse(string(content)) + rule, err := getRule(string(content)) if err != nil { return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err)) } diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go index ba2fbb0eb2f482dfde215c1b078ecad60e66bc14..4ccc19e95521d03ae1f663ec276646cf30926533 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -102,7 +102,7 @@ func (l *listenableRouter) Process(event *config_center.ConfigChangeEvent) { return } - routerRule, err := Parse(content) + routerRule, err := getRule(content) if err != nil { logger.Errorf("Parse condition router rule fail,error:[%s] ", err) return diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index c5d46444bde921386d14a8be7eb0a89d855f8ece..0267a3c7a462acb43f84ccb4701247147699804a 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -27,7 +27,6 @@ import ( ) import ( - matcher "github.com/apache/dubbo-go/cluster/router/match" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" @@ -301,7 +300,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { if !pair.Matches.Empty() && pair.Mismatches.Empty() { for match := range pair.Matches.Items { - if matcher.IsMatchGlobalPattern(match.(string), value, param) { + if isMatchGlobalPattern(match.(string), value, param) { return true } } @@ -310,7 +309,7 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { if !pair.Mismatches.Empty() && pair.Matches.Empty() { for mismatch := range pair.Mismatches.Items { - if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) { + if isMatchGlobalPattern(mismatch.(string), value, param) { return false } } @@ -319,12 +318,12 @@ func (pair MatchPair) isMatch(value string, param *common.URL) bool { if !pair.Mismatches.Empty() && !pair.Matches.Empty() { //when both mismatches and matches contain the same value, then using mismatches first for mismatch := range pair.Mismatches.Items { - if matcher.IsMatchGlobalPattern(mismatch.(string), value, param) { + if isMatchGlobalPattern(mismatch.(string), value, param) { return false } } for match := range pair.Matches.Items { - if matcher.IsMatchGlobalPattern(match.(string), value, param) { + if isMatchGlobalPattern(match.(string), value, param) { return true } } diff --git a/cluster/router/condition/router_rule.go b/cluster/router/condition/router_rule.go index 1374cf9de2585f78a27e3de99f356c6900268927..ce397d6cc0f51519123dd427709e8dba42d72a20 100644 --- a/cluster/router/condition/router_rule.go +++ b/cluster/router/condition/router_rule.go @@ -18,11 +18,17 @@ package condition import ( - "gopkg.in/yaml.v2" + "strings" +) + +import ( + gxstrings "github.com/dubbogo/gost/strings" ) import ( "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/yaml" ) // RouterRule RouterRule config read from config file or config center @@ -44,9 +50,9 @@ type RouterRule struct { * => * 1.1.1.1 */ -func Parse(rawRule string) (*RouterRule, error) { +func getRule(rawRule string) (*RouterRule, error) { r := &RouterRule{} - err := yaml.Unmarshal([]byte(rawRule), r) + err := yaml.UnmarshalYML([]byte(rawRule), r) if err != nil { return r, err } @@ -57,3 +63,11 @@ func Parse(rawRule string) (*RouterRule, error) { return r, nil } + +// isMatchGlobalPattern Match value to param content by pattern +func isMatchGlobalPattern(pattern string, value string, param *common.URL) bool { + if param != nil && strings.HasPrefix(pattern, "$") { + pattern = param.GetRawParam(pattern[1:]) + } + return gxstrings.IsMatchPattern(pattern, value) +} diff --git a/cluster/router/condition/router_rule_test.go b/cluster/router/condition/router_rule_test.go index 5acc7283917a7aa662b60cd90daba89d312db0cd..675acaec912b413d8fa3d1a25463b1fd4813a7f5 100644 --- a/cluster/router/condition/router_rule_test.go +++ b/cluster/router/condition/router_rule_test.go @@ -20,11 +20,16 @@ package condition import ( "testing" ) + import ( "github.com/stretchr/testify/assert" ) -func TestParse(t *testing.T) { +import ( + "github.com/apache/dubbo-go/common" +) + +func TestGetRule(t *testing.T) { testyml := ` scope: application runtime: true @@ -36,7 +41,7 @@ conditions: ip=127.0.0.1 => 1.1.1.1` - rule, e := Parse(testyml) + rule, e := getRule(testyml) assert.Nil(t, e) assert.NotNil(t, rule) @@ -50,3 +55,8 @@ conditions: assert.Equal(t, false, rule.Dynamic) assert.Equal(t, "", rule.Key) } + +func TestIsMatchGlobPattern(t *testing.T) { + url, _ := common.NewURL("dubbo://localhost:8080/Foo?key=v*e") + assert.Equal(t, true, isMatchGlobalPattern("$key", "value", &url)) +} diff --git a/cluster/router/healthcheck/default_health_check_test.go b/cluster/router/healthcheck/default_health_check_test.go index 74aa3940743a012f907cfe3d8811a618f07ff800..8a95d9a7e8dffdc3f30f94c76274a729837fc133 100644 --- a/cluster/router/healthcheck/default_health_check_test.go +++ b/cluster/router/healthcheck/default_health_check_test.go @@ -37,7 +37,7 @@ func TestDefaultHealthChecker_IsHealthy(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") hc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) - invoker := NewMockInvoker(url, 1) + invoker := NewMockInvoker(url) healthy := hc.IsHealthy(invoker) assert.True(t, healthy) diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go index a9d94da7c37f0e0c9640de1386998a85823e80a6..c3a26a93896e185f0dea3732ca5afcf7687ad5ea 100644 --- a/cluster/router/healthcheck/factory_test.go +++ b/cluster/router/healthcheck/factory_test.go @@ -35,7 +35,7 @@ type MockInvoker struct { url common.URL } -func NewMockInvoker(url common.URL, successCount int) *MockInvoker { +func NewMockInvoker(url common.URL) *MockInvoker { return &MockInvoker{ url: url, } diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go index 759ef93dbeb8d91a82eefd59060afbe8a10a4440..7bfffea705bfedade9d1d13ac7e9c380651335dd 100644 --- a/cluster/router/healthcheck/health_check_route_test.go +++ b/cluster/router/healthcheck/health_check_route_test.go @@ -44,9 +44,9 @@ func TestHealthCheckRouter_Route(t *testing.T) { hcr, _ := NewHealthCheckRouter(&consumerURL) var invokers []protocol.Invoker - invoker1 := NewMockInvoker(url1, 1) - invoker2 := NewMockInvoker(url2, 1) - invoker3 := NewMockInvoker(url3, 1) + invoker1 := NewMockInvoker(url1) + invoker2 := NewMockInvoker(url2) + invoker3 := NewMockInvoker(url3) invokers = append(invokers, invoker1, invoker2, invoker3) inv := invocation.NewRPCInvocation("test", nil, nil) res := hcr.Route(invokers, &consumerURL, inv) diff --git a/cluster/router/match/match_utils.go b/cluster/router/match/match_utils.go deleted file mode 100644 index 28fe7151c5126c41fbadf9f4d54da2b9df74a7fe..0000000000000000000000000000000000000000 --- a/cluster/router/match/match_utils.go +++ /dev/null @@ -1,63 +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 match - -import ( - "strings" -) - -import ( - "github.com/apache/dubbo-go/common" -) - -// IsMatchGlobalPattern Match value to param content by pattern -func IsMatchGlobalPattern(pattern string, value string, param *common.URL) bool { - if param != nil && strings.HasPrefix(pattern, "$") { - pattern = param.GetRawParam(pattern[1:]) - } - return isMatchInternalPattern(pattern, value) -} - -func isMatchInternalPattern(pattern string, value string) bool { - if "*" == pattern { - return true - } - if len(pattern) == 0 && len(value) == 0 { - return true - } - if len(pattern) == 0 || len(value) == 0 { - return false - } - i := strings.LastIndex(pattern, "*") - switch i { - case -1: - // doesn't find "*" - return value == pattern - case len(pattern) - 1: - // "*" is at the end - return strings.HasPrefix(value, pattern[0:i]) - case 0: - // "*" is at the beginning - return strings.HasSuffix(value, pattern[i+1:]) - default: - // "*" is in the middle - prefix := pattern[0:1] - suffix := pattern[i+1:] - return strings.HasPrefix(value, prefix) && strings.HasSuffix(value, suffix) - } -} diff --git a/cluster/router/router.go b/cluster/router/router.go index a28002a09e3b7217549b896d452f70997504ac8f..9ee1154437e6fd205f08098deabb1ca260c3c040 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -31,7 +31,7 @@ type RouterFactory interface { } // RouterFactory Router create factory use for parse config file -type FIleRouterFactory interface { +type FileRouterFactory interface { // NewFileRouters Create file router with config file NewFileRouter([]byte) (Router, error) } diff --git a/cluster/router/tag/factory.go b/cluster/router/tag/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..d74924c89862ae4f4cd85b59c7008880298c0c99 --- /dev/null +++ b/cluster/router/tag/factory.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 tag + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" +) + +func init() { + extension.SetRouterFactory(constant.TagRouterName, NewTagRouterFactory) +} + +type tagRouterFactory struct{} + +// NewTagRouterFactory create a tagRouterFactory +func NewTagRouterFactory() router.RouterFactory { + return &tagRouterFactory{} +} + +// NewRouter create a tagRouter by tagRouterFactory with a url +// The url contains router configuration information +func (c *tagRouterFactory) NewRouter(url *common.URL) (router.Router, error) { + return NewTagRouter(url) +} + +// NewFileRouter create a tagRouter by profile content +func (c *tagRouterFactory) NewFileRouter(content []byte) (router.Router, error) { + return NewFileTagRouter(content) +} diff --git a/cluster/router/tag/factory_test.go b/cluster/router/tag/factory_test.go new file mode 100644 index 0000000000000000000000000000000000000000..58bff5b18113d69f97ec513e393aa6759a3cf050 --- /dev/null +++ b/cluster/router/tag/factory_test.go @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tag + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +func TestTagRouterFactory_NewRouter(t *testing.T) { + u1, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true") + assert.Nil(t, err) + factory := NewTagRouterFactory() + tagRouter, e := factory.NewRouter(&u1) + assert.Nil(t, e) + assert.NotNil(t, tagRouter) +} diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go new file mode 100644 index 0000000000000000000000000000000000000000..8144c83203dbe98778dd6bb8dcdb9888be664b3b --- /dev/null +++ b/cluster/router/tag/file.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 tag + +import ( + "net/url" + "strconv" + "sync" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" +) + +// FileTagRouter Use for parse config file of Tag router +type FileTagRouter struct { + parseOnce sync.Once + router *tagRouter + routerRule *RouterRule + url *common.URL + force bool +} + +// NewFileTagRouter Create file tag router instance with content ( from config file) +func NewFileTagRouter(content []byte) (*FileTagRouter, error) { + fileRouter := &FileTagRouter{} + rule, err := getRule(string(content)) + if err != nil { + return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err)) + } + fileRouter.routerRule = rule + url := fileRouter.URL() + fileRouter.router, err = NewTagRouter(&url) + return fileRouter, err +} + +// URL Return URL in file tag router n +func (f *FileTagRouter) URL() common.URL { + f.parseOnce.Do(func() { + routerRule := f.routerRule + f.url = common.NewURLWithOptions( + common.WithProtocol(constant.TAG_ROUTE_PROTOCOL), + common.WithParams(url.Values{}), + common.WithParamsValue(constant.ForceUseTag, strconv.FormatBool(routerRule.Force)), + common.WithParamsValue(constant.RouterPriority, strconv.Itoa(routerRule.Priority)), + common.WithParamsValue(constant.ROUTER_KEY, constant.TAG_ROUTE_PROTOCOL)) + }) + return *f.url +} + +// Priority Return Priority in listenable router +func (f *FileTagRouter) Priority() int64 { + return f.router.priority +} + +func (f *FileTagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if len(invokers) == 0 { + return invokers + } + return f.Route(invokers, url, invocation) +} diff --git a/cluster/router/tag/file_test.go b/cluster/router/tag/file_test.go new file mode 100644 index 0000000000000000000000000000000000000000..94fcf9e0e0fabed2445417d14b711f91b65b9e5e --- /dev/null +++ b/cluster/router/tag/file_test.go @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tag + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common/constant" +) + +func TestNewFileTagRouter(t *testing.T) { + router, e := NewFileTagRouter([]byte(`priority: 100 +force: true`)) + assert.Nil(t, e) + assert.NotNil(t, router) + assert.Equal(t, 100, router.routerRule.Priority) + assert.Equal(t, true, router.routerRule.Force) +} + +func TestFileTagRouter_URL(t *testing.T) { + router, e := NewFileTagRouter([]byte(`priority: 100 +force: true`)) + assert.Nil(t, e) + assert.NotNil(t, router) + url := router.URL() + assert.NotNil(t, url) + force := url.GetParam(constant.ForceUseTag, "false") + priority := url.GetParam(constant.RouterPriority, "0") + assert.Equal(t, "true", force) + assert.Equal(t, "100", priority) + +} + +func TestFileTagRouter_Priority(t *testing.T) { + router, e := NewFileTagRouter([]byte(`priority: 100 +force: true`)) + assert.Nil(t, e) + assert.NotNil(t, router) + priority := router.Priority() + assert.Equal(t, int64(100), priority) +} diff --git a/cluster/router/tag/router_rule.go b/cluster/router/tag/router_rule.go new file mode 100644 index 0000000000000000000000000000000000000000..926446dcb2f18fa2fd4639a9246a85f435d75d45 --- /dev/null +++ b/cluster/router/tag/router_rule.go @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tag + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/common/yaml" +) + +// RouterRule RouterRule config read from config file or config center +type RouterRule struct { + router.BaseRouterRule `yaml:",inline""` +} + +func getRule(rawRule string) (*RouterRule, error) { + r := &RouterRule{} + err := yaml.UnmarshalYML([]byte(rawRule), r) + if err != nil { + return r, err + } + r.RawRule = rawRule + return r, nil +} diff --git a/cluster/router/tag/router_rule_test.go b/cluster/router/tag/router_rule_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2df65193f9d0cf607258f3080e22b42cd6e9b16a --- /dev/null +++ b/cluster/router/tag/router_rule_test.go @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tag + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestGetRule(t *testing.T) { + yml := ` +scope: application +runtime: true +force: true +` + rule, e := getRule(yml) + assert.Nil(t, e) + assert.NotNil(t, rule) + assert.Equal(t, true, rule.Force) + assert.Equal(t, true, rule.Runtime) + assert.Equal(t, "application", rule.Scope) +} diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go new file mode 100644 index 0000000000000000000000000000000000000000..87da418943e90c63a905f35260ada7880d6f51b9 --- /dev/null +++ b/cluster/router/tag/tag_router.go @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tag + +import ( + "strconv" +) + +import ( + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" +) + +type tagRouter struct { + url *common.URL + enabled bool + priority int64 +} + +func NewTagRouter(url *common.URL) (*tagRouter, error) { + if url == nil { + return nil, perrors.Errorf("Illegal route URL!") + } + return &tagRouter{ + url: url, + enabled: url.GetParamBool(constant.RouterEnabled, true), + priority: url.GetParamInt(constant.RouterPriority, 0), + }, nil +} + +func (c *tagRouter) isEnabled() bool { + return c.enabled +} + +func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if !c.isEnabled() { + return invokers + } + if len(invokers) == 0 { + return invokers + } + return filterUsingStaticTag(invokers, url, invocation) +} + +func (c *tagRouter) URL() common.URL { + return *c.url +} + +func (c *tagRouter) Priority() int64 { + return c.priority +} + +func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + if tag, ok := invocation.Attachments()[constant.Tagkey]; ok { + result := make([]protocol.Invoker, 0, 8) + for _, v := range invokers { + if v.GetUrl().GetParam(constant.Tagkey, "") == tag { + result = append(result, v) + } + } + if len(result) == 0 && !isForceUseTag(url, invocation) { + return invokers + } + return result + } + return invokers +} + +func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { + if b, e := strconv.ParseBool(invocation.AttachmentsByKey(constant.ForceUseTag, url.GetParam(constant.ForceUseTag, "false"))); e == nil { + return b + } + return false +} diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go new file mode 100644 index 0000000000000000000000000000000000000000..280b56c8ccb69eb5d32dae2369bdc862adb8e6fd --- /dev/null +++ b/cluster/router/tag/tag_router_test.go @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tag + +import ( + "context" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +// MockInvoker is only mock the Invoker to support test tagRouter +type MockInvoker struct { + url common.URL + available bool + destroyed bool + successCount int +} + +func NewMockInvoker(url common.URL) *MockInvoker { + return &MockInvoker{ + url: url, + available: true, + destroyed: false, + successCount: 0, + } +} + +func (bi *MockInvoker) GetUrl() common.URL { + return bi.url +} + +func (bi *MockInvoker) IsAvailable() bool { + return bi.available +} + +func (bi *MockInvoker) IsDestroyed() bool { + return bi.destroyed +} + +func (bi *MockInvoker) Invoke(_ context.Context, _ protocol.Invocation) protocol.Result { + bi.successCount++ + + result := &protocol.RPCResult{Err: nil} + return result +} + +func (bi *MockInvoker) Destroy() { + bi.destroyed = true + bi.available = false +} + +func TestTagRouter_Priority(t *testing.T) { + u1, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true") + assert.Nil(t, err) + tagRouter, e := NewTagRouter(&u1) + assert.Nil(t, e) + p := tagRouter.Priority() + assert.Equal(t, int64(0), p) +} + +func TestTagRouter_Route_force(t *testing.T) { + u1, e1 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true&dubbo.force.tag=true") + assert.Nil(t, e1) + tagRouter, e := NewTagRouter(&u1) + assert.Nil(t, e) + + u2, e2 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou") + u3, e3 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai") + u4, e4 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing") + assert.Nil(t, e2) + assert.Nil(t, e3) + assert.Nil(t, e4) + inv2 := NewMockInvoker(u2) + inv3 := NewMockInvoker(u3) + inv4 := NewMockInvoker(u4) + var invokers []protocol.Invoker + invokers = append(invokers, inv2, inv3, inv4) + inv := &invocation.RPCInvocation{} + inv.SetAttachments("dubbo.tag", "hangzhou") + invRst1 := tagRouter.Route(invokers, &u1, inv) + assert.Equal(t, 1, len(invRst1)) + assert.Equal(t, "hangzhou", invRst1[0].GetUrl().GetParam("dubbo.tag", "")) + + inv.SetAttachments("dubbo.tag", "guangzhou") + invRst2 := tagRouter.Route(invokers, &u1, inv) + assert.Equal(t, 0, len(invRst2)) + inv.SetAttachments("dubbo.force.tag", "false") + inv.SetAttachments("dubbo.tag", "guangzhou") + invRst3 := tagRouter.Route(invokers, &u1, inv) + assert.Equal(t, 3, len(invRst3)) +} + +func TestTagRouter_Route_noForce(t *testing.T) { + u1, e1 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserConsumer?interface=com.ikurento.user.UserConsumer&group=&version=2.6.0&enabled=true") + assert.Nil(t, e1) + tagRouter, e := NewTagRouter(&u1) + assert.Nil(t, e) + + u2, e2 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=hangzhou") + u3, e3 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=shanghai") + u4, e4 := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&enabled=true&dubbo.tag=beijing") + assert.Nil(t, e2) + assert.Nil(t, e3) + assert.Nil(t, e4) + inv2 := NewMockInvoker(u2) + inv3 := NewMockInvoker(u3) + inv4 := NewMockInvoker(u4) + var invokers []protocol.Invoker + invokers = append(invokers, inv2, inv3, inv4) + inv := &invocation.RPCInvocation{} + inv.SetAttachments("dubbo.tag", "hangzhou") + invRst := tagRouter.Route(invokers, &u1, inv) + assert.Equal(t, 1, len(invRst)) + assert.Equal(t, "hangzhou", invRst[0].GetUrl().GetParam("dubbo.tag", "")) + + inv.SetAttachments("dubbo.tag", "guangzhou") + inv.SetAttachments("dubbo.force.tag", "true") + invRst1 := tagRouter.Route(invokers, &u1, inv) + assert.Equal(t, 0, len(invRst1)) + inv.SetAttachments("dubbo.force.tag", "false") + invRst2 := tagRouter.Route(invokers, &u1, inv) + assert.Equal(t, 3, len(invRst2)) +} diff --git a/common/config/environment.go b/common/config/environment.go index 071af31152ba4ce3c579f70aa23df59d718ce506..446c46aa1ef71a68aa024bf83dd9088cf03677f2 100644 --- a/common/config/environment.go +++ b/common/config/environment.go @@ -46,7 +46,7 @@ var ( once sync.Once ) -// GetEnvInstance ... +// GetEnvInstance gets env instance by singleton func GetEnvInstance() *Environment { once.Do(func() { instance = &Environment{configCenterFirst: true} @@ -54,7 +54,7 @@ func GetEnvInstance() *Environment { return instance } -// NewEnvInstance ... +// NewEnvInstance creates Environment instance func NewEnvInstance() { instance = &Environment{configCenterFirst: true} } @@ -67,21 +67,22 @@ func NewEnvInstance() { // return env.configCenterFirst //} -// UpdateExternalConfigMap ... +// UpdateExternalConfigMap updates env externalConfigMap field func (env *Environment) UpdateExternalConfigMap(externalMap map[string]string) { for k, v := range externalMap { env.externalConfigMap.Store(k, v) } } -// UpdateAppExternalConfigMap ... +// UpdateAppExternalConfigMap updates env appExternalConfigMap field func (env *Environment) UpdateAppExternalConfigMap(externalMap map[string]string) { for k, v := range externalMap { env.appExternalConfigMap.Store(k, v) } } -// Configuration ... +// Configuration puts externalConfigMap and appExternalConfigMap into list +// List represents a doubly linked list. func (env *Environment) Configuration() *list.List { cfgList := list.New() // The sequence would be: SystemConfiguration -> ExternalConfiguration -> AppExternalConfiguration -> AbstractConfig -> PropertiesConfiguration @@ -90,17 +91,17 @@ func (env *Environment) Configuration() *list.List { return cfgList } -// SetDynamicConfiguration ... +// SetDynamicConfiguration sets value for dynamicConfiguration func (env *Environment) SetDynamicConfiguration(dc config_center.DynamicConfiguration) { env.dynamicConfiguration = dc } -// GetDynamicConfiguration ... +// GetDynamicConfiguration gets dynamicConfiguration func (env *Environment) GetDynamicConfiguration() config_center.DynamicConfiguration { return env.dynamicConfiguration } -// InmemoryConfiguration ... +// InmemoryConfiguration stores config in memory type InmemoryConfiguration struct { store *sync.Map } @@ -109,7 +110,7 @@ func newInmemoryConfiguration(p *sync.Map) *InmemoryConfiguration { return &InmemoryConfiguration{store: p} } -// GetProperty ... +// GetProperty gets value from InmemoryConfiguration instance by @key func (conf *InmemoryConfiguration) GetProperty(key string) (bool, string) { if conf.store == nil { return false, "" @@ -123,7 +124,7 @@ func (conf *InmemoryConfiguration) GetProperty(key string) (bool, string) { return false, "" } -// GetSubProperty ... +// GetSubProperty gets sub property from InmemoryConfiguration instance by @subkey func (conf *InmemoryConfiguration) GetSubProperty(subKey string) map[string]struct{} { if conf.store == nil { return nil diff --git a/common/constant/default.go b/common/constant/default.go index 3c889158e460031f06b9401008c80f55200a46e4..c1c404e089ea90899d2b599b01cd5980c3e92ab1 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -43,6 +43,7 @@ const ( DEFAULT_FAILBACK_TASKS = 100 DEFAULT_REST_CLIENT = "resty" DEFAULT_REST_SERVER = "go-restful" + DEFAULT_PORT = 20000 ) const ( @@ -58,6 +59,7 @@ const ( const ( ANY_VALUE = "*" ANYHOST_VALUE = "0.0.0.0" + LOCAL_HOST_VALUE = "192.168.1.1" REMOVE_VALUE_PREFIX = "-" ) @@ -74,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 27b77879b35b2d85c572853949cb26f40a2325ca..9c59575eb83e6d7742c9783b22fec14dd52ede73 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" @@ -41,6 +43,9 @@ const ( LOCAL_ADDR = "local-addr" REMOTE_ADDR = "remote-addr" PATH_SEPARATOR = "/" + DUBBO_KEY = "dubbo" + RELEASE_KEY = "release" + ANYHOST_KEY = "anyhost" ) const ( @@ -75,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 ( @@ -105,6 +115,7 @@ const ( ROUTERS_CATEGORY = "routers" ROUTE_PROTOCOL = "route" CONDITION_ROUTE_PROTOCOL = "condition" + TAG_ROUTE_PROTOCOL = "tag" PROVIDERS_CATEGORY = "providers" ROUTER_KEY = "router" ) @@ -116,6 +127,7 @@ const ( CONFIG_CLUSTER_KEY = "config.cluster" CONFIG_CHECK_KEY = "config.check" CONFIG_TIMEOUT_KET = "config.timeout" + CONFIG_LOG_DIR_KEY = "config.logDir" CONFIG_VERSION_KEY = "configVersion" COMPATIBLE_CONFIG_KEY = "compatible_config" ) @@ -162,7 +174,8 @@ const ( ListenableRouterName = "listenable" // HealthCheckRouterName Specify the name of HealthCheckRouter HealthCheckRouterName = "health_check" - + // TagRouterName Specify the name of TagRouter + TagRouterName = "tag" // ConditionRouterRuleSuffix Specify condition router suffix ConditionRouterRuleSuffix = ".condition-router" @@ -172,6 +185,13 @@ const ( RouterEnabled = "enabled" // Priority Priority key in router module RouterPriority = "priority" + + // ForceUseTag is the tag in attachment + ForceUseTag = "dubbo.force.tag" + Tagkey = "dubbo.tag" + + // Attachment key in context in invoker + AttachmentKey = "attachment" ) const ( @@ -202,9 +222,9 @@ const ( // consumer CONSUMER = "consumer" // key of access key id - ACCESS_KEY_ID_KEY = "accessKeyId" + ACCESS_KEY_ID_KEY = ".accessKeyId" // key of secret access key - SECRET_ACCESS_KEY_KEY = "secretAccessKey" + SECRET_ACCESS_KEY_KEY = ".secretAccessKey" ) // metadata report @@ -241,3 +261,21 @@ const ( // The default time window of circuit-tripped in millisecond if not specfied MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS = 30000 ) + +// service discovery +const ( + 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/auth.go b/common/extension/auth.go index a35fc509dae5b77a4e80fdd04171f90f337c668b..4fca0a8e8c255456720df9e4fd9852295715b160 100644 --- a/common/extension/auth.go +++ b/common/extension/auth.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package extension import ( @@ -9,13 +26,13 @@ var ( accesskeyStorages = make(map[string]func() filter.AccessKeyStorage) ) -// SetAuthenticator put the fcn into map with name +// SetAuthenticator puts the @fcn into map with name func SetAuthenticator(name string, fcn func() filter.Authenticator) { authenticators[name] = fcn } -// GetAuthenticator find the Authenticator with name -// if not found, it will panic +// GetAuthenticator finds the Authenticator with @name +// Panic if not found func GetAuthenticator(name string) filter.Authenticator { if authenticators[name] == nil { panic("authenticator for " + name + " is not existing, make sure you have import the package.") @@ -23,13 +40,13 @@ func GetAuthenticator(name string) filter.Authenticator { return authenticators[name]() } -// SetAccesskeyStorages will set the fcn into map with this name +// SetAccesskeyStorages will set the @fcn into map with this name func SetAccesskeyStorages(name string, fcn func() filter.AccessKeyStorage) { accesskeyStorages[name] = fcn } -// GetAccesskeyStorages find the storage with the name. -// If not found, it will panic. +// GetAccesskeyStorages finds the storage with the @name. +// Panic if not found func GetAccesskeyStorages(name string) filter.AccessKeyStorage { if accesskeyStorages[name] == nil { panic("accesskeyStorages for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/cluster.go b/common/extension/cluster.go index b2d81f6b1e56bb487b1d408b878308f6dfe042e4..8be27a1ca3aaf93dd54201c4ff7081478c746f0f 100644 --- a/common/extension/cluster.go +++ b/common/extension/cluster.go @@ -25,12 +25,13 @@ var ( clusters = make(map[string]func() cluster.Cluster) ) -// SetCluster ... +// SetCluster sets the cluster fault-tolerant mode with @name +// For example: available/failfast/broadcast/failfast/failsafe/... func SetCluster(name string, fcn func() cluster.Cluster) { clusters[name] = fcn } -// GetCluster ... +// GetCluster finds the cluster fault-tolerant mode with @name func GetCluster(name string) cluster.Cluster { if clusters[name] == nil { panic("cluster for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/config_center.go b/common/extension/config_center.go index 03d27db46c94b0ea0e212646077d97f948a8e328..5a2c52f32d070f5ec03bdae0b3cd47f869c28171 100644 --- a/common/extension/config_center.go +++ b/common/extension/config_center.go @@ -26,12 +26,12 @@ var ( configCenters = make(map[string]func(config *common.URL) (config_center.DynamicConfiguration, error)) ) -// SetConfigCenter ... -func SetConfigCenter(name string, v func(config *common.URL) (config_center.DynamicConfiguration, error)) { +// SetConfigCenter sets the DynamicConfiguration with @name +func SetConfigCenter(name string, v func(*common.URL) (config_center.DynamicConfiguration, error)) { configCenters[name] = v } -// GetConfigCenter ... +// GetConfigCenter finds the DynamicConfiguration with @name func GetConfigCenter(name string, config *common.URL) (config_center.DynamicConfiguration, error) { if configCenters[name] == nil { panic("config center for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/config_center_factory.go b/common/extension/config_center_factory.go index 85913fdce1ed3472c2bd9eb4aadbb0f631481dbd..dff89752296c6d2441d043ec628aa13ad219e698 100644 --- a/common/extension/config_center_factory.go +++ b/common/extension/config_center_factory.go @@ -25,12 +25,12 @@ var ( configCenterFactories = make(map[string]func() config_center.DynamicConfigurationFactory) ) -// SetConfigCenterFactory ... +// SetConfigCenterFactory sets the DynamicConfigurationFactory with @name func SetConfigCenterFactory(name string, v func() config_center.DynamicConfigurationFactory) { configCenterFactories[name] = v } -// GetConfigCenterFactory ... +// GetConfigCenterFactory finds the DynamicConfigurationFactory with @name func GetConfigCenterFactory(name string) config_center.DynamicConfigurationFactory { if configCenterFactories[name] == nil { panic("config center for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/config_reader.go b/common/extension/config_reader.go index aced5b0281ff9313461425e5ec6d70d562c6c947..5e13d8629fd145dac680619a427c68b29226b051 100644 --- a/common/extension/config_reader.go +++ b/common/extension/config_reader.go @@ -26,12 +26,12 @@ var ( defaults = make(map[string]string) ) -// SetConfigReaders set a creator of config reader. +// SetConfigReaders sets a creator of config reader with @name func SetConfigReaders(name string, v func() interfaces.ConfigReader) { configReaders[name] = v } -// GetConfigReaders get a config reader by name. +// GetConfigReaders gets a config reader with @name func GetConfigReaders(name string) interfaces.ConfigReader { if configReaders[name] == nil { panic("config reader for " + name + " is not existing, make sure you have imported the package.") @@ -39,12 +39,12 @@ func GetConfigReaders(name string) interfaces.ConfigReader { return configReaders[name]() } -// SetDefaultConfigReader set {name} to default config reader for {module} +// SetDefaultConfigReader sets @name for @module in default config reader func SetDefaultConfigReader(module, name string) { defaults[module] = name } -// GetDefaultConfigReader +// GetDefaultConfigReader gets default config reader func GetDefaultConfigReader() map[string]string { return defaults } diff --git a/common/extension/configurator.go b/common/extension/configurator.go index de98f8a260ea1f3a2e2a1f32c82dc869585e2789..dc2bea73afb79aaab36e2ce7cc9675169a446eb7 100644 --- a/common/extension/configurator.go +++ b/common/extension/configurator.go @@ -23,7 +23,7 @@ import ( ) const ( - // DefaultKey ... + // DefaultKey for default Configurator DefaultKey = "default" ) @@ -33,12 +33,12 @@ var ( configurator = make(map[string]getConfiguratorFunc) ) -// SetConfigurator ... +// SetConfigurator sets the getConfiguratorFunc with @name func SetConfigurator(name string, v getConfiguratorFunc) { configurator[name] = v } -// GetConfigurator ... +// GetConfigurator finds the Configurator with @name func GetConfigurator(name string, url *common.URL) config_center.Configurator { if configurator[name] == nil { panic("configurator for " + name + " is not existing, make sure you have import the package.") @@ -47,12 +47,12 @@ func GetConfigurator(name string, url *common.URL) config_center.Configurator { } -// SetDefaultConfigurator ... +// SetDefaultConfigurator sets the default Configurator func SetDefaultConfigurator(v getConfiguratorFunc) { configurator[DefaultKey] = v } -// GetDefaultConfigurator ... +// GetDefaultConfigurator gets default configurator func GetDefaultConfigurator(url *common.URL) config_center.Configurator { if configurator[DefaultKey] == nil { panic("configurator for default is not existing, make sure you have import the package.") @@ -61,7 +61,7 @@ func GetDefaultConfigurator(url *common.URL) config_center.Configurator { } -// GetDefaultConfiguratorFunc ... +// GetDefaultConfiguratorFunc gets default configurator function func GetDefaultConfiguratorFunc() getConfiguratorFunc { if configurator[DefaultKey] == nil { panic("configurator for default is not existing, make sure you have import the package.") diff --git a/common/extension/event_dispatcher.go b/common/extension/event_dispatcher.go new file mode 100644 index 0000000000000000000000000000000000000000..ac71e3b5e974f18dca6bf6ba50f2b552ef87d5c0 --- /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 already init. 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 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/filter.go b/common/extension/filter.go index deea2d908bc2741e0f15ecc36e9d4fc5975e531e..96059c4363060c41f14ececb466ca62bdaefb1a9 100644 --- a/common/extension/filter.go +++ b/common/extension/filter.go @@ -26,12 +26,13 @@ var ( rejectedExecutionHandler = make(map[string]func() filter.RejectedExecutionHandler) ) -// SetFilter ... +// SetFilter sets the filter extension with @name +// For example: hystrix/metrics/token/tracing/limit/... func SetFilter(name string, v func() filter.Filter) { filters[name] = v } -// GetFilter ... +// GetFilter finds the filter extension with @name func GetFilter(name string) filter.Filter { if filters[name] == nil { panic("filter for " + name + " is not existing, make sure you have imported the package.") @@ -39,12 +40,12 @@ func GetFilter(name string) filter.Filter { return filters[name]() } -// SetRejectedExecutionHandler ... +// SetRejectedExecutionHandler sets the RejectedExecutionHandler with @name func SetRejectedExecutionHandler(name string, creator func() filter.RejectedExecutionHandler) { rejectedExecutionHandler[name] = creator } -// GetRejectedExecutionHandler ... +// GetRejectedExecutionHandler finds the RejectedExecutionHandler with @name func GetRejectedExecutionHandler(name string) filter.RejectedExecutionHandler { creator, ok := rejectedExecutionHandler[name] if !ok { diff --git a/common/extension/graceful_shutdown.go b/common/extension/graceful_shutdown.go index 3abd75c0aa328f3553c3d83340ae440b8dfe3356..cb55419aabbce26b41e5b10f49268f6b3ace516d 100644 --- a/common/extension/graceful_shutdown.go +++ b/common/extension/graceful_shutdown.go @@ -49,7 +49,7 @@ func AddCustomShutdownCallback(callback func()) { customShutdownCallbacks.PushBack(callback) } -// GetAllCustomShutdownCallbacks ... +// GetAllCustomShutdownCallbacks gets all custom shutdown callbacks func GetAllCustomShutdownCallbacks() *list.List { return customShutdownCallbacks } diff --git a/common/extension/health_checker.go b/common/extension/health_checker.go index 365c5d0910812efb00eb94408bb226115b037c02..548d4dc761b31773a2a39ccb0ae3de1d7ab39eb4 100644 --- a/common/extension/health_checker.go +++ b/common/extension/health_checker.go @@ -26,12 +26,12 @@ var ( healthCheckers = make(map[string]func(url *common.URL) router.HealthChecker) ) -// SethealthChecker set the HealthChecker with name +// SethealthChecker sets the HealthChecker with @name func SethealthChecker(name string, fcn func(url *common.URL) router.HealthChecker) { healthCheckers[name] = fcn } -// GetHealthChecker get the HealthChecker with name +// GetHealthChecker gets the HealthChecker with @name func GetHealthChecker(name string, url *common.URL) router.HealthChecker { if healthCheckers[name] == nil { panic("healthCheckers for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/loadbalance.go b/common/extension/loadbalance.go index 0d557a4640ed892a18ad59a3247763ab5807a593..aa19141014a6c42df0c17dad05301997f67fbd79 100644 --- a/common/extension/loadbalance.go +++ b/common/extension/loadbalance.go @@ -25,12 +25,13 @@ var ( loadbalances = make(map[string]func() cluster.LoadBalance) ) -// SetLoadbalance ... +// SetLoadbalance sets the loadbalance extension with @name +// For example: random/round_robin/consistent_hash/least_active/... func SetLoadbalance(name string, fcn func() cluster.LoadBalance) { loadbalances[name] = fcn } -// GetLoadbalance ... +// GetLoadbalance finds the loadbalance extension with @name func GetLoadbalance(name string) cluster.LoadBalance { if loadbalances[name] == nil { panic("loadbalance for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/metadata_report_factory.go b/common/extension/metadata_report_factory.go index 0ae0793bb4459767cb42fb1860fc484388aae1a3..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 ... -func SetMetadataReportFactory(name string, v func() metadata.MetadataReportFactory) { +// SetMetadataReportFactory sets the MetadataReportFactory with @name +func SetMetadataReportFactory(name string, v func() factory.MetadataReportFactory) { metaDataReportFactories[name] = v } -// GetMetadataReportFactory ... -func GetMetadataReportFactory(name string) metadata.MetadataReportFactory { +// GetMetadataReportFactory finds the MetadataReportFactory with @name +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..e8c9e73d7362a83843f3cb6c52c0f3bd15ab1cc8 --- /dev/null +++ b/common/extension/metadata_service_proxy_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 ( + "fmt" +) + +import ( + "github.com/apache/dubbo-go/metadata/service" +) + +var ( + metadataServiceProxyFactoryMap = make(map[string]func() service.MetadataServiceProxyFactory) +) + +// SetMetadataServiceProxyFactory store the name-creator pair +func SetMetadataServiceProxyFactory(name string, creator func() service.MetadataServiceProxyFactory) { + 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/metrics.go b/common/extension/metrics.go index 42fca7a2db36614fcef31dd5ba7324a156164d4f..60cf6bac2384c7367094adad83e01f7dcf64a33d 100644 --- a/common/extension/metrics.go +++ b/common/extension/metrics.go @@ -27,12 +27,12 @@ var ( metricReporterMap = make(map[string]func() metrics.Reporter, 4) ) -// SetMetricReporter set a reporter with the name +// SetMetricReporter sets a reporter with the @name func SetMetricReporter(name string, reporterFunc func() metrics.Reporter) { metricReporterMap[name] = reporterFunc } -// GetMetricReporter find the reporter with name. +// GetMetricReporter finds the reporter with @name. // if not found, it will panic. // we should know that this method usually is called when system starts, so we should panic func GetMetricReporter(name string) metrics.Reporter { diff --git a/common/extension/protocol.go b/common/extension/protocol.go index 009687a17ace8cea567248af655e04604d09d9b8..c89dd08fae5d12b384d6ca4e797343fe79897bbd 100644 --- a/common/extension/protocol.go +++ b/common/extension/protocol.go @@ -25,12 +25,12 @@ var ( protocols = make(map[string]func() protocol.Protocol) ) -// SetProtocol ... +// SetProtocol sets the protocol extension with @name func SetProtocol(name string, v func() protocol.Protocol) { protocols[name] = v } -// GetProtocol ... +// GetProtocol finds the protocol extension with @name func GetProtocol(name string) protocol.Protocol { if protocols[name] == nil { panic("protocol for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/proxy_factory.go b/common/extension/proxy_factory.go index 19826bb0560ea0d3fa471c04873b20a6878f57d8..1e326d884b5dd37925c38ffdf0a87e69bf6a865c 100644 --- a/common/extension/proxy_factory.go +++ b/common/extension/proxy_factory.go @@ -25,12 +25,12 @@ var ( proxyFactories = make(map[string]func(...proxy.Option) proxy.ProxyFactory) ) -// SetProxyFactory ... +// SetProxyFactory sets the ProxyFactory extension with @name func SetProxyFactory(name string, f func(...proxy.Option) proxy.ProxyFactory) { proxyFactories[name] = f } -// GetProxyFactory ... +// GetProxyFactory finds the ProxyFactory extension with @name func GetProxyFactory(name string) proxy.ProxyFactory { if name == "" { name = "default" diff --git a/common/extension/registry.go b/common/extension/registry.go index 6ba746dc47382927d12ce39b7936212c5d75153d..542a2206c0bdda658c4ba363e939bbc569b2b49e 100644 --- a/common/extension/registry.go +++ b/common/extension/registry.go @@ -26,15 +26,15 @@ var ( registrys = make(map[string]func(config *common.URL) (registry.Registry, error)) ) -// SetRegistry ... +// SetRegistry sets the registry extension with @name func SetRegistry(name string, v func(config *common.URL) (registry.Registry, error)) { registrys[name] = v } -// GetRegistry ... +// GetRegistry finds the registry extension with @name func GetRegistry(name string, config *common.URL) (registry.Registry, error) { if registrys[name] == nil { - panic("registry for " + name + " is not existing, make sure you have import the package.") + panic("registry for " + name + " does not exist. please make sure that you have imported the package `github.com/apache/dubbo-go/registry/" + name + "`.") } return registrys[name](config) diff --git a/common/extension/registry_directory.go b/common/extension/registry_directory.go new file mode 100644 index 0000000000000000000000000000000000000000..330fc46400daf81047e5c24c1634249e355d74b7 --- /dev/null +++ b/common/extension/registry_directory.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 extension + +import ( + "github.com/apache/dubbo-go/cluster" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/registry" +) + +type registryDirectory func(url *common.URL, registry registry.Registry) (cluster.Directory, error) + +var defaultRegistry registryDirectory + +// SetDefaultRegistryDirectory sets the default registryDirectory +func SetDefaultRegistryDirectory(v registryDirectory) { + defaultRegistry = v +} + +// GetDefaultRegistryDirectory finds the registryDirectory with url and registry +func GetDefaultRegistryDirectory(config *common.URL, registry registry.Registry) (cluster.Directory, error) { + if defaultRegistry == nil { + panic("registry directory is not existing, make sure you have import the package.") + } + return defaultRegistry(config, registry) +} diff --git a/common/extension/rest_client.go b/common/extension/rest_client.go index 514d1fdfd2efb5c291fdb47df4dd69da26fa90b1..9caf8c67df76bb160d5e2c3100f83e2d198b6381 100644 --- a/common/extension/rest_client.go +++ b/common/extension/rest_client.go @@ -25,10 +25,12 @@ var ( restClients = make(map[string]func(restOptions *client.RestOptions) client.RestClient, 8) ) +// SetRestClient sets the RestClient with @name func SetRestClient(name string, fun func(restOptions *client.RestOptions) client.RestClient) { restClients[name] = fun } +// GetNewRestClient finds the RestClient with @name func GetNewRestClient(name string, restOptions *client.RestOptions) client.RestClient { if restClients[name] == nil { panic("restClient for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/rest_server.go b/common/extension/rest_server.go index fa8d435a5c976a4c95b036810fa2916a327a73b9..37a231a57c861ae49aab244eb9fa8b611ae63f6d 100644 --- a/common/extension/rest_server.go +++ b/common/extension/rest_server.go @@ -25,10 +25,12 @@ var ( restServers = make(map[string]func() server.RestServer, 8) ) +// SetRestServer sets the RestServer with @name func SetRestServer(name string, fun func() server.RestServer) { restServers[name] = fun } +// GetNewRestServer finds the RestServer with @name func GetNewRestServer(name string) server.RestServer { if restServers[name] == nil { panic("restServer for " + name + " is not existing, make sure you have import the package.") diff --git a/common/extension/router_factory.go b/common/extension/router_factory.go index 70d71dfa859b996030c865775a588da20039f9a5..21a49d2681b500bf4e4942d1b92e5b23bc7cf6b7 100644 --- a/common/extension/router_factory.go +++ b/common/extension/router_factory.go @@ -28,15 +28,15 @@ import ( var ( routers = make(map[string]func() router.RouterFactory) fileRouterFactoryOnce sync.Once - fileRouterFactories = make(map[string]router.FIleRouterFactory) + fileRouterFactories = make(map[string]router.FileRouterFactory) ) -// SetRouterFactory Set create router factory function by name +// SetRouterFactory sets create router factory function with @name func SetRouterFactory(name string, fun func() router.RouterFactory) { routers[name] = fun } -// GetRouterFactory Get create router factory function by name +// GetRouterFactory gets create router factory function by @name func GetRouterFactory(name string) router.RouterFactory { if routers[name] == nil { panic("router_factory for " + name + " is not existing, make sure you have import the package.") @@ -44,13 +44,13 @@ func GetRouterFactory(name string) router.RouterFactory { return routers[name]() } -// GetRouterFactories Get all create router factory function +// GetRouterFactories gets all create router factory function func GetRouterFactories() map[string]func() router.RouterFactory { return routers } -// GetFileRouterFactories Get all create file router factory instance -func GetFileRouterFactories() map[string]router.FIleRouterFactory { +// GetFileRouterFactories gets all create file router factory instance +func GetFileRouterFactories() map[string]router.FileRouterFactory { l := len(routers) if l == 0 { return nil @@ -58,7 +58,7 @@ func GetFileRouterFactories() map[string]router.FIleRouterFactory { fileRouterFactoryOnce.Do(func() { for k := range routers { factory := GetRouterFactory(k) - if fr, ok := factory.(router.FIleRouterFactory); ok { + if fr, ok := factory.(router.FileRouterFactory); ok { fileRouterFactories[k] = fr } } diff --git a/common/extension/service_discovery.go b/common/extension/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..456b14c83dcab4189ea287f2e9f3329109e538c3 --- /dev/null +++ b/common/extension/service_discovery.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 ( + perrors "github.com/pkg/errors" +) +import ( + "github.com/apache/dubbo-go/registry" +) + +var ( + discoveryCreatorMap = make(map[string]func(name string) (registry.ServiceDiscovery, error), 4) +) + +// SetServiceDiscovery will store the @creator and @name +// 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(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 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..7776a408d8ea3849907a4b12f5464aaa0e1e96aa --- /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) +) + +// 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..9e5aac52f90fd3fb68a342a91c7562c60e9c808c --- /dev/null +++ b/common/extension/service_name_mapping.go @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import ( + "github.com/apache/dubbo-go/metadata/mapping" +) + +var ( + globalNameMappingCreator func() mapping.ServiceNameMapping +) + +func SetGlobalServiceNameMapping(nameMappingCreator func() mapping.ServiceNameMapping) { + globalNameMappingCreator = nameMappingCreator +} + +func GetGlobalServiceNameMapping() mapping.ServiceNameMapping { + return globalNameMappingCreator() +} diff --git a/common/extension/tps_limit.go b/common/extension/tps_limit.go index c72c2b030fc0f391362189bfe18a65582543693a..d25821deee626cb75c94af2257f877c9983023de 100644 --- a/common/extension/tps_limit.go +++ b/common/extension/tps_limit.go @@ -26,12 +26,12 @@ var ( tpsLimiter = make(map[string]func() filter.TpsLimiter) ) -// SetTpsLimiter ... +// SetTpsLimiter sets the TpsLimiter with @name func SetTpsLimiter(name string, creator func() filter.TpsLimiter) { tpsLimiter[name] = creator } -// GetTpsLimiter ... +// GetTpsLimiter finds the TpsLimiter with @name func GetTpsLimiter(name string) filter.TpsLimiter { creator, ok := tpsLimiter[name] if !ok { @@ -41,12 +41,12 @@ func GetTpsLimiter(name string) filter.TpsLimiter { return creator() } -// SetTpsLimitStrategy ... +// SetTpsLimitStrategy sets the TpsLimitStrategyCreator with @name func SetTpsLimitStrategy(name string, creator filter.TpsLimitStrategyCreator) { tpsLimitStrategy[name] = creator } -// GetTpsLimitStrategyCreator ... +// GetTpsLimitStrategyCreator finds the TpsLimitStrategyCreator with @name func GetTpsLimitStrategyCreator(name string) filter.TpsLimitStrategyCreator { creator, ok := tpsLimitStrategy[name] if !ok { diff --git a/common/logger/logger.go b/common/logger/logger.go index 016afe69808f2007541c617f406db64beb511f1c..9bc6a461003d086e8951ebac3d6997774ac69b90 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -40,13 +40,13 @@ var ( logger Logger ) -// DubboLogger ... +// nolint type DubboLogger struct { Logger dynamicLevel zap.AtomicLevel } -// Logger ... +// Logger is the interface for Logger types type Logger interface { Info(args ...interface{}) Warn(args ...interface{}) @@ -67,7 +67,7 @@ func init() { } } -// InitLog ... +// InitLog use for init logger by call InitLogger func InitLog(logConfFile string) error { if logConfFile == "" { InitLogger(nil) @@ -96,7 +96,7 @@ func InitLog(logConfFile string) error { return nil } -// InitLogger ... +// InitLogger use for init logger by @conf func InitLogger(conf *zap.Config) { var zapLoggerConfig zap.Config if conf == nil { @@ -125,18 +125,18 @@ func InitLogger(conf *zap.Config) { getty.SetLogger(logger) } -// SetLogger ... +// SetLogger sets logger for dubbo and getty func SetLogger(log Logger) { logger = log getty.SetLogger(logger) } -// GetLogger ... +// GetLogger gets the logger func GetLogger() Logger { return logger } -// SetLoggerLevel ... +// SetLoggerLevel use for set logger level func SetLoggerLevel(level string) bool { if l, ok := logger.(OpsLogger); ok { l.SetLoggerLevel(level) @@ -145,13 +145,13 @@ func SetLoggerLevel(level string) bool { return false } -// OpsLogger ... +// OpsLogger use for the SetLoggerLevel type OpsLogger interface { Logger SetLoggerLevel(level string) } -// SetLoggerLevel ... +// SetLoggerLevel use for set logger level func (dl *DubboLogger) SetLoggerLevel(level string) { l := new(zapcore.Level) l.Set(level) diff --git a/common/logger/logging.go b/common/logger/logging.go index 36d48ee61e8a4a986abfbaa79f3d361cd81494f4..7a31ece203815287384ade282b2a4f12e11abc2a 100644 --- a/common/logger/logging.go +++ b/common/logger/logging.go @@ -17,42 +17,42 @@ package logger -// Info ... +// Info is info level func Info(args ...interface{}) { logger.Info(args...) } -// Warn ... +// Warn is warning level func Warn(args ...interface{}) { logger.Warn(args...) } -// Error ... +// Error is error level func Error(args ...interface{}) { logger.Error(args...) } -// Debug ... +// Debug is debug level func Debug(args ...interface{}) { logger.Debug(args...) } -// Infof ... +// Infof is format info level func Infof(fmt string, args ...interface{}) { logger.Infof(fmt, args...) } -// Warnf ... +// Warnf is format warning level func Warnf(fmt string, args ...interface{}) { logger.Warnf(fmt, args...) } -// Errorf ... +// Errorf is format error level func Errorf(fmt string, args ...interface{}) { logger.Errorf(fmt, args...) } -// Debugf ... +// Debugf is format debug level func Debugf(fmt string, args ...interface{}) { logger.Debugf(fmt, args...) } diff --git a/common/node.go b/common/node.go index 979eee31ef3a63eb21af6c9045aee7f6d784f2ba..4febd78536126c67bdc65fc09d4be47fb869ef5e 100644 --- a/common/node.go +++ b/common/node.go @@ -17,7 +17,7 @@ package common -// Node ... +// Node use for process dubbo node type Node interface { GetUrl() URL IsAvailable() bool 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..887f7a377d31fbb29f5b696f295c3dde07fb8daf --- /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) +} + +// GetAllEventListeners get all listener +// using RLock +func (bl *BaseListener) GetAllEventListeners() []EventListener { + allListenersSlice := make([]EventListener, 0, 16) + + bl.Mutex.RLock() + defer bl.Mutex.RUnlock() + for _, listenersSlice := range bl.ListenersCache { + allListenersSlice = append(allListenersSlice, listenersSlice...) + } + sort.Slice(allListenersSlice, func(i, j int) bool { + return allListenersSlice[i].GetPriority() < allListenersSlice[j].GetPriority() + }) + return allListenersSlice +} + +// containListener true if contain listener +// it's not thread safe +// usually it should be use in lock scope +func containListener(listenersSlice []EventListener, listener EventListener) bool { + for _, loadListener := range listenersSlice { + if loadListener == listener { + return true + } + } + return false +} diff --git a/common/observer/listenable_test.go b/common/observer/listenable_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5a03382a937fe925b6e17b495d066b86c8d2161d --- /dev/null +++ b/common/observer/listenable_test.go @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package observer + +import ( + "reflect" + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestListenable(t *testing.T) { + el := &TestEventListener{} + bl := NewBaseListener() + b := &bl + b.AddEventListener(el) + b.AddEventListener(el) + al := b.GetAllEventListeners() + assert.Equal(t, len(al), 1) + assert.Equal(t, al[0].GetEventType(), reflect.TypeOf(TestEvent{})) + b.RemoveEventListener(el) + assert.Equal(t, len(b.GetAllEventListeners()), 0) + var ts []EventListener + ts = append(ts, el) + b.AddEventListeners(ts) + assert.Equal(t, len(al), 1) + +} + +type TestEvent struct { + BaseEvent +} + +type TestEventListener struct { + EventListener +} + +func (tel *TestEventListener) OnEvent(e Event) error { + return nil +} + +func (tel *TestEventListener) GetPriority() int { + return -1 +} + +func (tel *TestEventListener) GetEventType() reflect.Type { + return reflect.TypeOf(TestEvent{}) +} diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index 68ba3ff7882837a9419c5e47228461af11fd79ba..abcf87cd9d297769bf8aff6fa07d6a4659091eb6 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -25,12 +25,13 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" invocation_impl "github.com/apache/dubbo-go/protocol/invocation" ) -// Proxy struct +// nolint type Proxy struct { rpc common.RPCService invoke protocol.Invoker @@ -44,7 +45,7 @@ var ( typError = reflect.Zero(reflect.TypeOf((*error)(nil)).Elem()).Type() ) -// NewProxy ... +// NewProxy create service proxy. func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[string]string) *Proxy { return &Proxy{ invoke: invoke, @@ -59,7 +60,6 @@ func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[str // type XxxProvider struct { // Yyy func(ctx context.Context, args []interface{}, rsp *Zzz) error // } - func (p *Proxy) Implement(v common.RPCService) { // check parameters, incoming interface must be a elem's pointer. @@ -141,7 +141,7 @@ func (p *Proxy) Implement(v common.RPCService) { } // add user setAttachment - atm := invCtx.Value("attachment") + atm := invCtx.Value(constant.AttachmentKey) if m, ok := atm.(map[string]string); ok { for k, value := range m { inv.SetAttachments(k, value) @@ -149,6 +149,9 @@ func (p *Proxy) Implement(v common.RPCService) { } result := p.invoke.Invoke(invCtx, inv) + if len(result.Attachments()) > 0 { + invCtx = context.WithValue(invCtx, constant.AttachmentKey, result.Attachments()) + } err = result.Error() logger.Debugf("[makeDubboCallProxy] result: %v, err: %v", result.Result(), err) @@ -202,12 +205,12 @@ func (p *Proxy) Implement(v common.RPCService) { } -// Get ... +// Get gets rpc service instance. func (p *Proxy) Get() common.RPCService { return p.rpc } -// GetCallback ... +// GetCallback gets callback. func (p *Proxy) GetCallback() interface{} { return p.callBack } diff --git a/common/proxy/proxy_factory.go b/common/proxy/proxy_factory.go index 7b249a3e9754b097130a80bf3819d282dad6b6e8..117428cb253e1ad4a4ceee59aa620d7097b41a75 100644 --- a/common/proxy/proxy_factory.go +++ b/common/proxy/proxy_factory.go @@ -22,12 +22,12 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// ProxyFactory ... +// ProxyFactory interface. type ProxyFactory interface { GetProxy(invoker protocol.Invoker, url *common.URL) *Proxy GetAsyncProxy(invoker protocol.Invoker, callBack interface{}, url *common.URL) *Proxy GetInvoker(url common.URL) protocol.Invoker } -// Option ... +// Option will define a function of handling ProxyFactory type Option func(ProxyFactory) diff --git a/common/proxy/proxy_factory/default.go b/common/proxy/proxy_factory/default.go index 114cfee2363022da5f7957a825a16fc42b8c928f..1bb1e29c5ced78ad9e2e2483b73379c66328050a 100644 --- a/common/proxy/proxy_factory/default.go +++ b/common/proxy/proxy_factory/default.go @@ -40,7 +40,7 @@ func init() { extension.SetProxyFactory("default", NewDefaultProxyFactory) } -// DefaultProxyFactory ... +// DefaultProxyFactory is the default proxy factory type DefaultProxyFactory struct { //delegate ProxyFactory } @@ -53,17 +53,17 @@ type DefaultProxyFactory struct { // } //} -// NewDefaultProxyFactory ... +// NewDefaultProxyFactory returns a proxy factory instance func NewDefaultProxyFactory(options ...proxy.Option) proxy.ProxyFactory { return &DefaultProxyFactory{} } -// GetProxy ... +// GetProxy gets a proxy func (factory *DefaultProxyFactory) GetProxy(invoker protocol.Invoker, url *common.URL) *proxy.Proxy { return factory.GetAsyncProxy(invoker, nil, url) } -// GetAsyncProxy ... +// GetAsyncProxy gets a async proxy func (factory *DefaultProxyFactory) GetAsyncProxy(invoker protocol.Invoker, callBack interface{}, url *common.URL) *proxy.Proxy { //create proxy attachments := map[string]string{} @@ -71,19 +71,19 @@ func (factory *DefaultProxyFactory) GetAsyncProxy(invoker protocol.Invoker, call return proxy.NewProxy(invoker, callBack, attachments) } -// GetInvoker ... +// GetInvoker gets a invoker func (factory *DefaultProxyFactory) GetInvoker(url common.URL) protocol.Invoker { return &ProxyInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), } } -// ProxyInvoker ... +// ProxyInvoker is a invoker struct type ProxyInvoker struct { protocol.BaseInvoker } -// Invoke ... +// Invoke is used to call service method by invocation func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { result := &protocol.RPCResult{} result.SetAttachments(invocation.Attachments()) @@ -113,6 +113,7 @@ func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocati in := []reflect.Value{svc.Rcvr()} if method.CtxType() != nil { + ctx = context.WithValue(ctx, constant.AttachmentKey, invocation.Attachments()) in = append(in, method.SuiteContext(ctx)) } diff --git a/common/rpc_service.go b/common/rpc_service.go index b235c32abc9a971d7144605c8b4b82953ac8f3c4..f9fb145c802ead8732031efe11deb1653dec2bb3 100644 --- a/common/rpc_service.go +++ b/common/rpc_service.go @@ -35,23 +35,23 @@ import ( ) // RPCService -//rpc service interface +// rpc service interface type RPCService interface { // Reference: // rpc service id or reference id Reference() string } -//AsyncCallbackService callback interface for async +// AsyncCallbackService callback interface for async type AsyncCallbackService interface { // Callback: callback CallBack(response CallbackResponse) } -//CallbackResponse for different protocol +// CallbackResponse for different protocol type CallbackResponse interface{} -//AsyncCallback async callback method +// AsyncCallback async callback method type AsyncCallback func(response CallbackResponse) // for lowercase func @@ -59,7 +59,6 @@ type AsyncCallback func(response CallbackResponse) // return map[string][string]{} // } const ( - // METHOD_MAPPER ... METHOD_MAPPER = "MethodMapper" ) @@ -68,10 +67,11 @@ var ( // because Typeof takes an empty interface value. This is annoying. typeOfError = reflect.TypeOf((*error)(nil)).Elem() - // ServiceMap ... + // ServiceMap store description of service. // todo: lowerecas? ServiceMap = &serviceMap{ - serviceMap: make(map[string]map[string]*Service), + serviceMap: make(map[string]map[string]*Service), + interfaceMap: make(map[string][]*Service), } ) @@ -79,7 +79,7 @@ var ( // info of method ////////////////////////// -// MethodType ... +// MethodType is description of service method. type MethodType struct { method reflect.Method ctxType reflect.Type // request context @@ -87,27 +87,27 @@ type MethodType struct { replyType reflect.Type // return value, otherwise it is nil } -// Method ... +// Method gets @m.method. func (m *MethodType) Method() reflect.Method { return m.method } -// CtxType ... +// CtxType gets @m.ctxType. func (m *MethodType) CtxType() reflect.Type { return m.ctxType } -// ArgsType ... +// ArgsType gets @m.argsType. func (m *MethodType) ArgsType() []reflect.Type { return m.argsType } -// ReplyType ... +// ReplyType gets @m.replyType. func (m *MethodType) ReplyType() reflect.Type { return m.replyType } -// SuiteContext ... +// SuiteContext tranfers @ctx to reflect.Value type or get it from @m.ctxType. func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value { if contextv := reflect.ValueOf(ctx); contextv.IsValid() { return contextv @@ -119,7 +119,7 @@ func (m *MethodType) SuiteContext(ctx context.Context) reflect.Value { // info of service interface ////////////////////////// -// Service ... +// Service is description of service type Service struct { name string rcvr reflect.Value @@ -127,17 +127,22 @@ type Service struct { methods map[string]*MethodType } -// Method ... +// Method gets @s.methods. func (s *Service) Method() map[string]*MethodType { return s.methods } -// RcvrType ... +// 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 } -// Rcvr ... +// Rcvr gets @s.rcvr. func (s *Service) Rcvr() reflect.Value { return s.rcvr } @@ -147,10 +152,12 @@ func (s *Service) Rcvr() reflect.Value { ////////////////////////// type serviceMap struct { - mutex sync.RWMutex // protects the serviceMap - serviceMap map[string]map[string]*Service // protocol -> service name -> service + mutex sync.RWMutex // protects the serviceMap + serviceMap map[string]map[string]*Service // protocol -> service name -> service + interfaceMap map[string][]*Service // interface -> service } +// GetService gets a service defination by protocol and name func (sm *serviceMap) GetService(protocol, name string) *Service { sm.mutex.RLock() defer sm.mutex.RUnlock() @@ -163,10 +170,24 @@ func (sm *serviceMap) GetService(protocol, name string) *Service { return nil } -func (sm *serviceMap) Register(protocol string, rcvr RPCService) (string, error) { +// GetInterface gets an interface defination by interface name +func (sm *serviceMap) GetInterface(interfaceName string) []*Service { + sm.mutex.RLock() + defer sm.mutex.RUnlock() + if s, ok := sm.interfaceMap[interfaceName]; ok { + return s + } + return nil +} + +// Register registers a service by @interfaceName and @protocol +func (sm *serviceMap) Register(interfaceName, protocol string, rcvr RPCService) (string, error) { if sm.serviceMap[protocol] == nil { sm.serviceMap[protocol] = make(map[string]*Service) } + if sm.interfaceMap[interfaceName] == nil { + sm.interfaceMap[interfaceName] = make([]*Service, 0, 16) + } s := new(Service) s.rcvrType = reflect.TypeOf(rcvr) @@ -201,32 +222,65 @@ func (sm *serviceMap) Register(protocol string, rcvr RPCService) (string, error) } sm.mutex.Lock() sm.serviceMap[protocol][s.name] = s + sm.interfaceMap[interfaceName] = append(sm.interfaceMap[interfaceName], s) sm.mutex.Unlock() return strings.TrimSuffix(methods, ","), nil } -func (sm *serviceMap) UnRegister(protocol, serviceId string) error { +// UnRegister cancels a service by @interfaceName, @protocol and @serviceId +func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) error { if protocol == "" || serviceId == "" { return perrors.New("protocol or serviceName is nil") } - sm.mutex.RLock() - svcs, ok := sm.serviceMap[protocol] - if !ok { - sm.mutex.RUnlock() - return perrors.New("no services for " + protocol) + + var ( + err error + index = -1 + svcs map[string]*Service + svrs []*Service + ok bool + ) + + f := func() error { + sm.mutex.RLock() + defer sm.mutex.RUnlock() + svcs, ok = sm.serviceMap[protocol] + if !ok { + return perrors.New("no services for " + protocol) + } + s, ok := svcs[serviceId] + if !ok { + return perrors.New("no service for " + serviceId) + } + svrs, ok = sm.interfaceMap[interfaceName] + if !ok { + return perrors.New("no service for " + interfaceName) + } + for i, svr := range svrs { + if svr == s { + index = i + } + } + return nil } - _, ok = svcs[serviceId] - if !ok { - sm.mutex.RUnlock() - return perrors.New("no service for " + serviceId) + + if err = f(); err != nil { + return err } - sm.mutex.RUnlock() sm.mutex.Lock() defer sm.mutex.Unlock() + sm.interfaceMap[interfaceName] = make([]*Service, 0, len(svrs)) + for i, _ := range svrs { + if i != index { + sm.interfaceMap[interfaceName] = append(sm.interfaceMap[interfaceName], svrs[i]) + } + } delete(svcs, serviceId) - delete(sm.serviceMap, protocol) + if len(sm.serviceMap) == 0 { + delete(sm.serviceMap, protocol) + } return nil } @@ -289,6 +343,13 @@ func suiteMethod(method reflect.Method) *MethodType { argsType []reflect.Type ) + // this method is in RPCService + // we force users must implement RPCService interface in their provider + // 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/rpc_service_test.go b/common/rpc_service_test.go index 8c9b9d15cdd4061dbe2f445b5fff7a868e5ae67e..2311205d0ec0c2fd4642a4d8639c0bf871fe1d17 100644 --- a/common/rpc_service_test.go +++ b/common/rpc_service_test.go @@ -77,46 +77,48 @@ func TestServiceMap_Register(t *testing.T) { // lowercase s0 := &testService{} // methods, err := ServiceMap.Register("testporotocol", s0) - _, err := ServiceMap.Register("testporotocol", s0) + _, err := ServiceMap.Register("testService", "testporotocol", s0) assert.EqualError(t, err, "type testService is not exported") // succ s := &TestService{} - methods, err := ServiceMap.Register("testporotocol", s) + methods, err := ServiceMap.Register("testService", "testporotocol", s) assert.NoError(t, err) assert.Equal(t, "MethodOne,MethodThree,methodTwo", methods) // repeat - _, err = ServiceMap.Register("testporotocol", s) + _, err = ServiceMap.Register("testService", "testporotocol", s) assert.EqualError(t, err, "service already defined: com.test.Path") // no method s1 := &TestService1{} - _, err = ServiceMap.Register("testporotocol", s1) + _, err = ServiceMap.Register("testService", "testporotocol", s1) assert.EqualError(t, err, "type com.test.Path1 has no exported methods of suitable type") ServiceMap = &serviceMap{ - serviceMap: make(map[string]map[string]*Service), + serviceMap: make(map[string]map[string]*Service), + interfaceMap: make(map[string][]*Service), } } func TestServiceMap_UnRegister(t *testing.T) { s := &TestService{} - _, err := ServiceMap.Register("testprotocol", s) + _, err := ServiceMap.Register("TestService", "testprotocol", s) assert.NoError(t, err) assert.NotNil(t, ServiceMap.GetService("testprotocol", "com.test.Path")) + assert.Equal(t, 1, len(ServiceMap.GetInterface("TestService"))) - err = ServiceMap.UnRegister("", "com.test.Path") + err = ServiceMap.UnRegister("", "", "com.test.Path") assert.EqualError(t, err, "protocol or serviceName is nil") - err = ServiceMap.UnRegister("protocol", "com.test.Path") + err = ServiceMap.UnRegister("", "protocol", "com.test.Path") assert.EqualError(t, err, "no services for protocol") - err = ServiceMap.UnRegister("testprotocol", "com.test.Path1") + err = ServiceMap.UnRegister("", "testprotocol", "com.test.Path1") assert.EqualError(t, err, "no service for com.test.Path1") // succ - err = ServiceMap.UnRegister("testprotocol", "com.test.Path") + err = ServiceMap.UnRegister("TestService", "testprotocol", "com.test.Path") assert.NoError(t, err) } diff --git a/common/url.go b/common/url.go index ebb648db27c3efff534f0d0a545f2211f335aa89..e0e15739daa926f02280dc69e70ed38f673c1ae5 100644 --- a/common/url.go +++ b/common/url.go @@ -18,7 +18,6 @@ package common import ( - "bytes" "encoding/base64" "fmt" "math" @@ -26,7 +25,6 @@ import ( "net/url" "strconv" "strings" - "sync" ) import ( @@ -38,151 +36,161 @@ import ( import ( "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" ) -///////////////////////////////// +// /////////////////////////////// // dubbo role type -///////////////////////////////// +// /////////////////////////////// // role constant const ( - // CONSUMER ... + // CONSUMER is consumer role CONSUMER = iota - // CONFIGURATOR ... + // CONFIGURATOR is configurator role CONFIGURATOR - // ROUTER ... + // ROUTER is router role ROUTER - // PROVIDER ... + // PROVIDER is provider role PROVIDER ) var ( - // DubboNodes ... + // DubboNodes Dubbo service node DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"} // DubboRole Dubbo service role DubboRole = [...]string{"consumer", "", "routers", "provider"} ) -// RoleType ... +// nolint type RoleType int func (t RoleType) String() string { return DubboNodes[t] } -// Role ... +// Role returns role by @RoleType func (t RoleType) Role() string { return DubboRole[t] } type baseUrl struct { - Protocol string - Location string // ip+port - Ip string - Port string - //url.Values is not safe map, add to avoid concurrent map read and map write error - paramsLock sync.RWMutex + Protocol string + Location string // ip+port + Ip string + Port string params url.Values PrimitiveURL string } -// URL ... +// URL is not thread-safe. +// we fail to define this struct to be immutable object. +// but, those method which will update the URL, including SetParam, SetParams +// are only allowed to be invoked in creating URL instance +// Please keep in mind that this struct is immutable after it has been created and initialized. type URL struct { baseUrl Path string // like /com.ikurento.dubbo.UserProvider3 Username string Password string Methods []string - //special for registry + // special for registry SubURL *URL } +// Option accepts url +// Option will define a function of handling URL type option func(*URL) -// WithUsername ... +// WithUsername sets username for url func WithUsername(username string) option { return func(url *URL) { url.Username = username } } -// WithPassword ... +// WithPassword sets password for url func WithPassword(pwd string) option { return func(url *URL) { url.Password = pwd } } -// WithMethods ... +// WithMethods sets methods for url func WithMethods(methods []string) option { return func(url *URL) { url.Methods = methods } } -// WithParams ... +// WithParams sets params for url func WithParams(params url.Values) option { return func(url *URL) { url.params = params } } -// WithParamsValue ... +// WithParamsValue sets params field for url func WithParamsValue(key, val string) option { return func(url *URL) { url.SetParam(key, val) } } -// WithProtocol ... +// WithProtocol sets protocol for url func WithProtocol(proto string) option { return func(url *URL) { url.Protocol = proto } } -// WithIp ... +// WithIp sets ip for url func WithIp(ip string) option { return func(url *URL) { url.Ip = ip } } -// WithPort ... +// WithPort sets port for url func WithPort(port string) option { return func(url *URL) { url.Port = port } } -// WithPath ... +// WithPath sets path for url func WithPath(path string) option { return func(url *URL) { url.Path = "/" + strings.TrimPrefix(path, "/") } } -// WithLocation ... +// WithLocation sets location for url func WithLocation(location string) option { return func(url *URL) { url.Location = location } } -// WithToken ... +// WithToken sets token for url func WithToken(token string) option { return func(url *URL) { if len(token) > 0 { value := token if strings.ToLower(token) == "true" || strings.ToLower(token) == "default" { - value = uuid.NewV4().String() + u, err := uuid.NewV4() + if err != nil { + logger.Errorf("could not generator UUID: %v", err) + return + } + value = u.String() } url.SetParam(constant.TOKEN_KEY, value) } } } -// NewURLWithOptions ... +// NewURLWithOptions will create a new url with options func NewURLWithOptions(opts ...option) *URL { url := &URL{} for _, opt := range opts { @@ -195,7 +203,6 @@ func NewURLWithOptions(opts ...option) *URL { // NewURL will create a new url // the urlString should not be empty func NewURL(urlString string, opts ...option) (URL, error) { - var ( err error rawUrlString string @@ -213,7 +220,7 @@ func NewURL(urlString string, opts ...option) (URL, error) { return s, perrors.Errorf("url.QueryUnescape(%s), error{%v}", urlString, err) } - //rawUrlString = "//" + rawUrlString + // rawUrlString = "//" + rawUrlString if strings.Index(rawUrlString, "//") < 0 { t := URL{baseUrl: baseUrl{}} for _, opt := range opts { @@ -249,7 +256,7 @@ func NewURL(urlString string, opts ...option) (URL, error) { return s, nil } -// URLEqual ... +// URLEqual judge @url and @c is equal or not. func (c URL) URLEqual(url URL) bool { c.Ip = "" c.Port = "" @@ -265,17 +272,19 @@ func (c URL) URLEqual(url URL) bool { } else if urlGroup == constant.ANY_VALUE { urlKey = strings.Replace(urlKey, "group=*", "group="+cGroup, 1) } + + // 1. protocol, username, password, ip, port, service name, group, version should be equal if cKey != urlKey { return false } + + // 2. if url contains enabled key, should be true, or * if url.GetParam(constant.ENABLED_KEY, "true") != "true" && url.GetParam(constant.ENABLED_KEY, "") != constant.ANY_VALUE { return false } - //TODO :may need add interface key any value condition - if !isMatchCategory(url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), c.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY)) { - return false - } - return true + + // TODO :may need add interface key any value condition + return isMatchCategory(url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), c.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY)) } func isMatchCategory(category1 string, category2 string) bool { @@ -291,38 +300,35 @@ func isMatchCategory(category1 string, category2 string) bool { } func (c URL) String() string { - var buildString string + var buf strings.Builder if len(c.Username) == 0 && len(c.Password) == 0 { - buildString = fmt.Sprintf( + buf.WriteString(fmt.Sprintf( "%s://%s:%s%s?", - c.Protocol, c.Ip, c.Port, c.Path) + c.Protocol, c.Ip, c.Port, c.Path)) } else { - buildString = fmt.Sprintf( + buf.WriteString(fmt.Sprintf( "%s://%s:%s@%s:%s%s?", - c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path) + c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path)) } - c.paramsLock.RLock() - buildString += c.params.Encode() - c.paramsLock.RUnlock() - return buildString + buf.WriteString(c.params.Encode()) + return buf.String() } -// Key ... +// Key gets key func (c URL) Key() string { buildString := fmt.Sprintf( "%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s", c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Service(), c.GetParam(constant.GROUP_KEY, ""), c.GetParam(constant.VERSION_KEY, "")) return buildString - //return c.ServiceKey() } -// ServiceKey ... +// ServiceKey gets a unique key of a service. func (c URL) ServiceKey() string { intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) if intf == "" { return "" } - buf := &bytes.Buffer{} + var buf strings.Builder group := c.GetParam(constant.GROUP_KEY, "") if group != "" { buf.WriteString(group) @@ -347,7 +353,7 @@ func (c *URL) ColonSeparatedKey() string { if intf == "" { return "" } - buf := &bytes.Buffer{} + var buf strings.Builder buf.WriteString(intf) buf.WriteString(":") version := c.GetParam(constant.VERSION_KEY, "") @@ -362,44 +368,44 @@ func (c *URL) ColonSeparatedKey() string { return buf.String() } -// EncodedServiceKey ... +// EncodedServiceKey encode the service key func (c *URL) EncodedServiceKey() string { serviceKey := c.ServiceKey() return strings.Replace(serviceKey, "/", "*", 1) } -// Service ... +// Service gets service func (c URL) Service() string { service := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) if service != "" { return service } else if c.SubURL != nil { service = c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) - if service != "" { //if url.path is "" then return suburl's path, special for registry url + if service != "" { // if url.path is "" then return suburl's path, special for registry url return service } } return "" } -// AddParam ... +// AddParam will add the key-value pair +// Not thread-safe +// think twice before using it. func (c *URL) AddParam(key string, value string) { - c.paramsLock.Lock() c.params.Add(key, value) - c.paramsLock.Unlock() } -// SetParam ... +// SetParam will put the key-value pair into url +// it's not thread safe. +// think twice before you want to use this method +// usually it should only be invoked when you want to initialized an url func (c *URL) SetParam(key string, value string) { - c.paramsLock.Lock() c.params.Set(key, value) - c.paramsLock.Unlock() } -// RangeParams ... +// RangeParams will iterate the params +// it's not thread-safe func (c *URL) RangeParams(f func(key, value string) bool) { - c.paramsLock.RLock() - defer c.paramsLock.RUnlock() for k, v := range c.params { if !f(k, v[0]) { break @@ -407,32 +413,28 @@ func (c *URL) RangeParams(f func(key, value string) bool) { } } -// GetParam ... +// GetParam gets value by key func (c URL) GetParam(s string, d string) string { - var r string - c.paramsLock.RLock() - if r = c.params.Get(s); len(r) == 0 { + r := c.params.Get(s) + if len(r) == 0 { r = d } - c.paramsLock.RUnlock() return r } -// GetParams ... +// GetParams gets values func (c URL) GetParams() url.Values { return c.params } -// GetParamAndDecoded ... +// GetParamAndDecoded gets values and decode func (c URL) GetParamAndDecoded(key string) (string, error) { - c.paramsLock.RLock() - defer c.paramsLock.RUnlock() ruleDec, err := base64.URLEncoding.DecodeString(c.GetParam(key, "")) value := string(ruleDec) return value, err } -// GetRawParam ... +// GetRawParam gets raw param func (c URL) GetRawParam(key string) string { switch key { case "protocol": @@ -452,76 +454,61 @@ func (c URL) GetRawParam(key string) string { } } -// GetParamBool ... -func (c URL) GetParamBool(s string, d bool) bool { - - var r bool - var err error - if r, err = strconv.ParseBool(c.GetParam(s, "")); err != nil { +// GetParamBool judge whether @key exists or not +func (c URL) GetParamBool(key string, d bool) bool { + r, err := strconv.ParseBool(c.GetParam(key, "")) + if err != nil { return d } return r } -// GetParamInt ... -func (c URL) GetParamInt(s string, d int64) int64 { - var r int - var err error - - if r, err = strconv.Atoi(c.GetParam(s, "")); r == 0 || err != nil { +// GetParamInt gets int value by @key +func (c URL) GetParamInt(key string, d int64) int64 { + r, err := strconv.Atoi(c.GetParam(key, "")) + if r == 0 || err != nil { return d } return int64(r) } -// GetMethodParamInt ... +// GetMethodParamInt gets int method param func (c URL) GetMethodParamInt(method string, key string, d int64) int64 { - var r int - var err error - c.paramsLock.RLock() - defer c.paramsLock.RUnlock() - if r, err = strconv.Atoi(c.GetParam("methods."+method+"."+key, "")); r == 0 || err != nil { + r, err := strconv.Atoi(c.GetParam("methods."+method+"."+key, "")) + if r == 0 || err != nil { return d } return int64(r) } -// GetMethodParamInt64 ... +// GetMethodParamInt64 gets int64 method param func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 { r := c.GetMethodParamInt(method, key, math.MinInt64) if r == math.MinInt64 { return c.GetParamInt(key, d) } - return r } -// GetMethodParam ... +// GetMethodParam gets method param func (c URL) GetMethodParam(method string, key string, d string) string { - var r string - if r = c.GetParam("methods."+method+"."+key, ""); r == "" { + r := c.GetParam("methods."+method+"."+key, "") + if r == "" { r = d } return r } -// GetMethodParamBool ... +// GetMethodParamBool judge whether @method param exists or not func (c URL) GetMethodParamBool(method string, key string, d bool) bool { r := c.GetParamBool("methods."+method+"."+key, d) return r } -// RemoveParams ... -func (c *URL) RemoveParams(set *gxset.HashSet) { - c.paramsLock.Lock() - defer c.paramsLock.Unlock() - for k := range set.Items { - s := k.(string) - delete(c.params, s) - } -} - -// SetParams ... +// SetParams will put all key-value pair into url. +// 1. if there already has same key, the value will be override +// 2. it's not thread safe +// 3. think twice when you want to invoke this method func (c *URL) SetParams(m url.Values) { for k := range m { c.SetParam(k, m.Get(k)) @@ -530,7 +517,6 @@ func (c *URL) SetParams(m url.Values) { // ToMap transfer URL to Map func (c URL) ToMap() map[string]string { - paramsMap := make(map[string]string) c.RangeParams(func(key, value string) bool { @@ -571,29 +557,35 @@ func (c URL) ToMap() map[string]string { // configuration > reference config >service config // in this function we should merge the reference local url config into the service url from registry. -//TODO configuration merge, in the future , the configuration center's config should merge too. - -// MergeUrl ... +// TODO configuration merge, in the future , the configuration center's config should merge too. + +// MergeUrl will merge those two url +// the result is based on serviceUrl, and the key which si only contained in referenceUrl +// will be added into result. +// for example, if serviceUrl contains params (a1->v1, b1->v2) and referenceUrl contains params(a2->v3, b1 -> v4) +// the params of result will be (a1->v1, b1->v2, a2->v3). +// You should notice that the value of b1 is v2, not v4. +// due to URL is not thread-safe, so this method is not thread-safe func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL { mergedUrl := serviceUrl.Clone() - //iterator the referenceUrl if serviceUrl not have the key ,merge in + // iterator the referenceUrl if serviceUrl not have the key ,merge in referenceUrl.RangeParams(func(key, value string) bool { if v := mergedUrl.GetParam(key, ""); len(v) == 0 { mergedUrl.SetParam(key, value) } return true }) - //loadBalance,cluster,retries strategy config + // loadBalance,cluster,retries strategy config methodConfigMergeFcn := mergeNormalParam(mergedUrl, referenceUrl, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY, constant.TIMEOUT_KEY}) - //remote timestamp + // remote timestamp if v := serviceUrl.GetParam(constant.TIMESTAMP_KEY, ""); len(v) > 0 { mergedUrl.SetParam(constant.REMOTE_TIMESTAMP_KEY, v) mergedUrl.SetParam(constant.TIMESTAMP_KEY, referenceUrl.GetParam(constant.TIMESTAMP_KEY, "")) } - //finally execute methodConfigMergeFcn + // finally execute methodConfigMergeFcn for _, method := range referenceUrl.Methods { for _, fcn := range methodConfigMergeFcn { fcn("methods." + method) @@ -603,7 +595,7 @@ func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL { return mergedUrl } -// Clone ... +// Clone will copy the url func (c *URL) Clone() *URL { newUrl := &URL{} copier.Copy(newUrl, c) @@ -615,8 +607,43 @@ func (c *URL) Clone() *URL { return newUrl } +func (c *URL) CloneExceptParams(excludeParams *gxset.HashSet) *URL { + newUrl := &URL{} + copier.Copy(newUrl, c) + newUrl.params = url.Values{} + c.RangeParams(func(key, value string) bool { + if !excludeParams.Contains(key) { + newUrl.SetParam(key, value) + } + return true + }) + return newUrl +} + +// Copy url based on the reserved parameter's keys. +func (c *URL) CloneWithParams(reserveParams []string) *URL { + params := url.Values{} + for _, reserveParam := range reserveParams { + v := c.GetParam(reserveParam, "") + if len(v) != 0 { + params.Set(reserveParam, v) + } + } + + return NewURLWithOptions( + WithProtocol(c.Protocol), + WithUsername(c.Username), + WithPassword(c.Password), + WithIp(c.Ip), + WithPort(c.Port), + WithPath(c.Path), + WithMethods(c.Methods), + WithParams(params), + ) +} + func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []func(method string) { - var methodConfigMergeFcn = []func(method string){} + methodConfigMergeFcn := make([]func(method string), 0, len(paramKeys)) for _, paramKey := range paramKeys { if v := referenceUrl.GetParam(paramKey, ""); len(v) > 0 { mergedUrl.SetParam(paramKey, v) @@ -629,3 +656,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/common/url_test.go b/common/url_test.go index 2372de520e88b0949023e88cec64871736dd6aa0..4d9dff9f373f5d2250deb577621cead8c991cf4d 100644 --- a/common/url_test.go +++ b/common/url_test.go @@ -118,6 +118,33 @@ func TestURL_URLEqual(t *testing.T) { u3, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") assert.NoError(t, err) assert.False(t, u1.URLEqual(u3)) + + // urlGroupAnyValue's group is * + urlGroupAnyValue, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0") + assert.NoError(t, err) + assert.True(t, u3.URLEqual(urlGroupAnyValue)) + + // test for enabled + urlEnabledEmpty, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=") + assert.NoError(t, err) + assert.True(t, u3.URLEqual(urlEnabledEmpty)) + + urlEnabledFalse, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=1") + assert.NoError(t, err) + assert.False(t, u3.URLEqual(urlEnabledFalse)) + + urlEnabledTrue, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=true") + assert.NoError(t, err) + assert.True(t, u3.URLEqual(urlEnabledTrue)) + + urlEnabledAny, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=*") + assert.NoError(t, err) + assert.True(t, u3.URLEqual(urlEnabledAny)) + + // test for category + categoryAny, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=*&version=2.6.0&enabled=*&category=*") + assert.NoError(t, err) + assert.True(t, categoryAny.URLEqual(u3)) } func TestURL_GetParam(t *testing.T) { diff --git a/common/yaml/yaml.go b/common/yaml/yaml.go index 7c31d71c35fff547d2ed0a765e8245717148a451..5edda1b3c7751e8171528d121148b6c3c60fe128 100644 --- a/common/yaml/yaml.go +++ b/common/yaml/yaml.go @@ -40,7 +40,7 @@ func LoadYMLConfig(confProFile string) ([]byte, error) { return ioutil.ReadFile(confProFile) } -// unmarshalYMLConfig Load yml config byte from file , then unmarshal to object +// unmarshalYMLConfig Load yml config byte from file, then unmarshal to object func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) { confFileStream, err := LoadYMLConfig(confProFile) if err != nil { @@ -48,3 +48,7 @@ func UnmarshalYMLConfig(confProFile string, out interface{}) ([]byte, error) { } return confFileStream, yaml.Unmarshal(confFileStream, out) } + +func UnmarshalYML(data []byte, out interface{}) error { + return yaml.Unmarshal(data, out) +} diff --git a/common/yaml/yaml_test.go b/common/yaml/yaml_test.go index 45eee59048c1c074b9c386e26cc7a2252896e6ea..c8b8258a68951a1437ac2e617c13ee5af4b3a5ee 100644 --- a/common/yaml/yaml_test.go +++ b/common/yaml/yaml_test.go @@ -46,6 +46,18 @@ func TestUnmarshalYMLConfig_Error(t *testing.T) { assert.Error(t, err) } +func TestUnmarshalYML(t *testing.T) { + c := &Config{} + b, err := LoadYMLConfig("./testdata/config.yml") + assert.NoError(t, err) + err = UnmarshalYML(b, c) + assert.NoError(t, err) + assert.Equal(t, "strTest", c.StrTest) + assert.Equal(t, 11, c.IntTest) + assert.Equal(t, false, c.BooleanTest) + assert.Equal(t, "childStrTest", c.ChildConfig.StrTest) +} + type Config struct { StrTest string `yaml:"strTest" default:"default" json:"strTest,omitempty" property:"strTest"` IntTest int `default:"109" yaml:"intTest" json:"intTest,omitempty" property:"intTest"` diff --git a/config/application_config.go b/config/application_config.go index 33b47c81dd0da9959984cd1f53648167863cb713..ef99664fa298c28365ed7acc54d0c18a88c9b5c2 100644 --- a/config/application_config.go +++ b/config/application_config.go @@ -25,33 +25,24 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -// ApplicationConfig ... +// ApplicationConfig is a configuration for current application, whether the application is a provider or a consumer type ApplicationConfig struct { - Organization string `yaml:"organization" json:"organization,omitempty" property:"organization"` + Organization string `yaml:"organization" json:"organization,omitempty" property:"organization"` Name string `yaml:"name" json:"name,omitempty" property:"name"` Module string `yaml:"module" json:"module,omitempty" property:"module"` 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"` } -// Prefix ... +// nolint func (*ApplicationConfig) Prefix() string { return constant.DUBBO + ".application." } -// Id ... -func (c *ApplicationConfig) Id() string { - return "" -} - -// SetId ... -func (c *ApplicationConfig) SetId(id string) { - -} - -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the ApplicationConfig by @unmarshal function func (c *ApplicationConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err diff --git a/config/base_config.go b/config/base_config.go index 93c0ce6a6692193e7ea7b1b9f2f74e9eaed0c858..46ff5fb174b9204b00c7d7a0369615ca377fd2d4 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,12 +84,11 @@ func (c *BaseConfig) startConfigCenter() error { if c.prepareEnvironment() != nil { return perrors.WithMessagef(err, "start config center error!") } - //c.fresh() + // c.fresh() return err } func (c *BaseConfig) prepareEnvironment() error { - factory := extension.GetConfigCenterFactory(c.ConfigCenterConfig.Protocol) dynamicConfig, err := factory.GetDynamicConfiguration(c.configCenterUrl) config.GetEnvInstance().SetDynamicConfiguration(dynamicConfig) @@ -102,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 { @@ -265,7 +284,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, "|") { @@ -280,7 +299,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) @@ -315,7 +334,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()) @@ -323,7 +342,7 @@ func (c *BaseConfig) freshInternalConfig(config *config.InmemoryConfiguration) { setFieldValue(val, reflect.Value{}, config) } -// SetFatherConfig ... +// SetFatherConfig sets father config by @fatherConfig func (c *BaseConfig) SetFatherConfig(fatherConfig interface{}) { c.fatherConfig = fatherConfig } @@ -361,7 +380,6 @@ func initializeStruct(t reflect.Type, v reflect.Value) { default: } } - } } } diff --git a/config/base_config_test.go b/config/base_config_test.go index d16b2420922ece60ef2135729cd47d5aa73a3760..ea53ca5208cd0fdee4ab2fb22f5c895a524381aa 100644 --- a/config/base_config_test.go +++ b/config/base_config_test.go @@ -21,9 +21,11 @@ import ( "reflect" "testing" ) + import ( "github.com/stretchr/testify/assert" ) + import ( "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/extension" @@ -45,23 +47,17 @@ func Test_refresh(t *testing.T) { father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + BaseConfig: BaseConfig{ + ApplicationConfig: &ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "2.6.0", + Owner: "dubbo", + Environment: "test"}, + }, + Registries: map[string]*RegistryConfig{ - //"shanghai_reg1": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, "shanghai_reg2": { Protocol: "mock", TimeoutStr: "2s", @@ -87,6 +83,7 @@ func Test_refresh(t *testing.T) { Password: "pwd1", }, }, + References: map[string]*ReferenceConfig{ "MockService": { InterfaceName: "com.MockService", @@ -148,23 +145,17 @@ func Test_appExternal_refresh(t *testing.T) { config.GetEnvInstance().UpdateExternalConfigMap(mockMap) father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + 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": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, "shanghai_reg2": { Protocol: "mock", TimeoutStr: "2s", @@ -243,23 +234,17 @@ func Test_appExternalWithoutId_refresh(t *testing.T) { config.GetEnvInstance().UpdateExternalConfigMap(mockMap) father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + 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": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, "shanghai_reg2": { Protocol: "mock", TimeoutStr: "2s", @@ -285,6 +270,7 @@ func Test_appExternalWithoutId_refresh(t *testing.T) { Password: "pwd1", }, }, + References: map[string]*ReferenceConfig{ "MockService": { InterfaceName: "com.MockService", @@ -337,15 +323,20 @@ func Test_refresh_singleRegistry(t *testing.T) { father := &ConsumerConfig{ Check: &[]bool{true}[0], - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + BaseConfig: BaseConfig{ + + ApplicationConfig: &ApplicationConfig{ + Organization: "dubbo_org", + Name: "dubbo", + Module: "module", + Version: "2.6.0", + Owner: "dubbo", + Environment: "test"}, + }, + Registries: map[string]*RegistryConfig{}, Registry: &RegistryConfig{}, + References: map[string]*ReferenceConfig{ "MockService": { InterfaceName: "com.MockService", @@ -400,23 +391,17 @@ func Test_refreshProvider(t *testing.T) { config.GetEnvInstance().UpdateExternalConfigMap(mockMap) father := &ProviderConfig{ - ApplicationConfig: &ApplicationConfig{ - Organization: "dubbo_org", - Name: "dubbo", - Module: "module", - Version: "2.6.0", - Owner: "dubbo", - Environment: "test"}, + 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": { - // id: "shanghai_reg1", - // Protocol: "mock", - // TimeoutStr: "2s", - // Group: "shanghai_idc", - // Address: "127.0.0.1:2181", - // Username: "user1", - // Password: "pwd1", - //}, "shanghai_reg2": { Protocol: "mock", TimeoutStr: "2s", @@ -442,6 +427,7 @@ func Test_refreshProvider(t *testing.T) { Password: "pwd1", }, }, + Services: map[string]*ServiceConfig{ "MockService": { InterfaceName: "com.MockService", @@ -481,7 +467,6 @@ func Test_refreshProvider(t *testing.T) { } func Test_startConfigCenter(t *testing.T) { - extension.SetConfigCenterFactory("mock", func() config_center.DynamicConfigurationFactory { return &config_center.MockDynamicConfigurationFactory{} }) @@ -499,21 +484,21 @@ func Test_startConfigCenter(t *testing.T) { } func Test_initializeStruct(t *testing.T) { - consumerConfig := &ConsumerConfig{} + testConsumerConfig := &ConsumerConfig{} tp := reflect.TypeOf(ConsumerConfig{}) v := reflect.New(tp) initializeStruct(tp, v.Elem()) - fmt.Println(reflect.ValueOf(consumerConfig).Elem().Type().String()) + fmt.Println(reflect.ValueOf(testConsumerConfig).Elem().Type().String()) fmt.Println(v.Elem().Type().String()) - reflect.ValueOf(consumerConfig).Elem().Set(v.Elem()) + reflect.ValueOf(testConsumerConfig).Elem().Set(v.Elem()) assert.Condition(t, func() (success bool) { - return consumerConfig.ApplicationConfig != nil + return testConsumerConfig.Registry != nil }) assert.Condition(t, func() (success bool) { - return consumerConfig.Registries != nil + return testConsumerConfig.Registries != nil }) assert.Condition(t, func() (success bool) { - return consumerConfig.References != nil + return testConsumerConfig.References != nil }) } diff --git a/config/config_center_config.go b/config/config_center_config.go index 40b9b6517186a8a4f7956db3d23f0a1cdfbdc8cb..c9133dc26df0b05e3bb61df0f612d0e2914e98bb 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -31,7 +31,13 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -// ConfigCenterConfig ... +// ConfigCenterConfig is configuration for config center +// +// ConfigCenter also introduced concepts of namespace and group to better manage Key-Value pairs by group, +// those configs are already built-in in many professional third-party configuration centers. +// In most cases, namespace is used to isolate different tenants, while group is used to divide the key set from one tenant into groups. +// +// ConfigCenter has currently supported Zookeeper, Nacos, Etcd, Consul, Apollo type ConfigCenterConfig struct { context context.Context Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty"` @@ -40,6 +46,7 @@ type ConfigCenterConfig struct { Group string `default:"dubbo" yaml:"group" json:"group,omitempty"` Username string `yaml:"username" json:"username,omitempty"` Password string `yaml:"password" json:"password,omitempty"` + LogDir string `yaml:"log_dir" json:"log_dir,omitempty"` ConfigFile string `default:"dubbo.properties" yaml:"config_file" json:"config_file,omitempty"` Namespace string `default:"dubbo" yaml:"namespace" json:"namespace,omitempty"` AppConfigFile string `default:"dubbo.properties" yaml:"app_config_file" json:"app_config_file,omitempty"` @@ -48,7 +55,7 @@ type ConfigCenterConfig struct { timeout time.Duration } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the ConfigCenterConfig by @unmarshal function func (c *ConfigCenterConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -60,12 +67,13 @@ func (c *ConfigCenterConfig) UnmarshalYAML(unmarshal func(interface{}) error) er return nil } -// GetUrlMap ... +// GetUrlMap gets url map from ConfigCenterConfig func (c *ConfigCenterConfig) GetUrlMap() url.Values { urlMap := url.Values{} urlMap.Set(constant.CONFIG_NAMESPACE_KEY, c.Namespace) urlMap.Set(constant.CONFIG_GROUP_KEY, c.Group) urlMap.Set(constant.CONFIG_CLUSTER_KEY, c.Cluster) urlMap.Set(constant.CONFIG_APP_ID_KEY, c.AppId) + urlMap.Set(constant.CONFIG_LOG_DIR_KEY, c.LogDir) return urlMap } diff --git a/config/config_loader.go b/config/config_loader.go index c0687d8fc162331afc5098e347d4bbba6a1750c6..e0cb09d9423c6ff25611981cb548fb2732cdadfe 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -21,6 +21,7 @@ import ( "fmt" "log" "os" + "sync" "time" ) @@ -33,15 +34,23 @@ 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 + // baseConfigOnce is used to make sure that we only create it once. + baseConfigOnce sync.Once + + // configAccessMutex is used to make sure that BaseConfig.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 +68,15 @@ func init() { if errCon := ConsumerInit(confConFile); errCon != nil { log.Printf("[consumerInit] %#v", errCon) consumerConfig = nil + } else { + baseConfig = &consumerConfig.BaseConfig } if errPro := ProviderInit(confProFile); errPro != nil { log.Printf("[providerInit] %#v", errPro) providerConfig = nil + } else { + baseConfig = &providerConfig.BaseConfig } } @@ -81,128 +94,146 @@ func checkApplicationName(config *ApplicationConfig) { } } -// Load Dubbo Init -func Load() { - - // init router - if confRouterFile != "" { - if errPro := RouterInit(confRouterFile); errPro != nil { - log.Printf("[routerConfig init] %#v", errPro) - } - } - - // reference config +func loadConsumerConfig() { if consumerConfig == nil { logger.Warnf("consumerConfig is nil!") - } else { - // init other consumer config - conConfigType := consumerConfig.ConfigType - for key, value := range extension.GetDefaultConfigReader() { - if conConfigType == nil { - if v, ok := conConfigType[key]; ok { - value = v - } - } - if err := extension.GetConfigReaders(value).ReadConsumerConfig(consumerConfig.fileStream); err != nil { - logger.Errorf("ReadConsumerConfig error: %#v for %s", perrors.WithStack(err), value) + return + } + // init other consumer config + conConfigType := consumerConfig.ConfigType + for key, value := range extension.GetDefaultConfigReader() { + if conConfigType == nil { + if v, ok := conConfigType[key]; ok { + value = v } } + if err := extension.GetConfigReaders(value).ReadConsumerConfig(consumerConfig.fileStream); err != nil { + logger.Errorf("ReadConsumerConfig error: %#v for %s", perrors.WithStack(err), value) + } + } - metricConfig = consumerConfig.MetricConfig - applicationConfig = consumerConfig.ApplicationConfig - - checkApplicationName(consumerConfig.ApplicationConfig) - if err := configCenterRefreshConsumer(); err != nil { - logger.Errorf("[consumer config center refresh] %#v", err) + checkApplicationName(consumerConfig.ApplicationConfig) + if err := configCenterRefreshConsumer(); err != nil { + logger.Errorf("[consumer config center refresh] %#v", err) + } + checkRegistries(consumerConfig.Registries, consumerConfig.Registry) + for key, ref := range consumerConfig.References { + if ref.Generic { + genericService := NewGenericService(key) + SetConsumerService(genericService) } - checkRegistries(consumerConfig.Registries, consumerConfig.Registry) - for key, ref := range consumerConfig.References { - if ref.Generic { - genericService := NewGenericService(key) - SetConsumerService(genericService) - } - rpcService := GetConsumerService(key) - if rpcService == nil { - logger.Warnf("%s does not exist!", key) - continue - } - ref.id = key - ref.Refer(rpcService) - ref.Implement(rpcService) + rpcService := GetConsumerService(key) + if rpcService == nil { + logger.Warnf("%s does not exist!", key) + continue } + ref.id = key + ref.Refer(rpcService) + ref.Implement(rpcService) + } - //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 - - if refconfig.invoker != nil && - !refconfig.invoker.IsAvailable() { - checkok = false - count++ - if count > maxWait { - errMsg := fmt.Sprintf("Failed to check the status of the service %v . No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version) - logger.Error(errMsg) - panic(errMsg) - } - time.Sleep(time.Second * 1) - break - } - if refconfig.invoker == nil { - logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName) + // 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 + + if refconfig.invoker != nil && + !refconfig.invoker.IsAvailable() { + checkok = false + count++ + if count > maxWait { + errMsg := fmt.Sprintf("Failed to check the status of the service %v . No provider available for the service to the consumer use dubbo version %v", refconfig.InterfaceName, constant.Version) + logger.Error(errMsg) + panic(errMsg) } + time.Sleep(time.Second * 1) + break + } + if refconfig.invoker == nil { + logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName) } } - if checkok { - break - } - checkok = true } + if checkok { + break + } + checkok = true } +} - // service config +func loadProviderConfig() { if providerConfig == nil { logger.Warnf("providerConfig is nil!") - } else { - // init other provider config - proConfigType := providerConfig.ConfigType - for key, value := range extension.GetDefaultConfigReader() { - if proConfigType != nil { - if v, ok := proConfigType[key]; ok { - value = v - } - } - if err := extension.GetConfigReaders(value).ReadProviderConfig(providerConfig.fileStream); err != nil { - logger.Errorf("ReadProviderConfig error: %#v for %s", perrors.WithStack(err), value) + return + } + + // init other provider config + proConfigType := providerConfig.ConfigType + for key, value := range extension.GetDefaultConfigReader() { + if proConfigType != nil { + if v, ok := proConfigType[key]; ok { + value = v } } + if err := extension.GetConfigReaders(value).ReadProviderConfig(providerConfig.fileStream); err != nil { + logger.Errorf("ReadProviderConfig error: %#v for %s", perrors.WithStack(err), value) + } + } - // 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) - checkApplicationName(providerConfig.ApplicationConfig) - if err := configCenterRefreshProvider(); err != nil { - logger.Errorf("[provider config center refresh] %#v", err) + for key, svs := range providerConfig.Services { + rpcService := GetProviderService(key) + if rpcService == nil { + logger.Warnf("%s does not exist!", key) + continue } - checkRegistries(providerConfig.Registries, providerConfig.Registry) - for key, svs := range providerConfig.Services { - rpcService := GetProviderService(key) - if rpcService == nil { - logger.Warnf("%s does not exist!", key) - continue - } - svs.id = key - svs.Implement(rpcService) - if err := svs.Export(); err != nil { - panic(fmt.Sprintf("service %s export failed! ", key)) - } + 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)) + } + } +} + +func initRouter() { + if confRouterFile != "" { + if err := RouterInit(confRouterFile); err != nil { + log.Printf("[routerConfig init] %#v", err) } } +} + +// Load Dubbo Init +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() + + // service config + loadProviderConfig() + // init the shutdown callback GracefulShutdownInit() } @@ -219,39 +250,77 @@ 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 { + baseConfigOnce.Do(func() { + 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 498f82678070d194e3ffe1539064be7aec19f719..a239621dd0e43fb940e6e94ea87df62b9123cd26 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 ( @@ -82,14 +83,15 @@ func TestLoad(t *testing.T) { conServices = map[string]common.RPCService{} proServices = map[string]common.RPCService{} - common.ServiceMap.UnRegister("mock", "MockService") + err := common.ServiceMap.UnRegister("com.MockService", "mock", "MockService") + assert.Nil(t, err) consumerConfig = nil providerConfig = nil } func TestLoadWithSingleReg(t *testing.T) { doInitConsumerWithSingleRegistry() - doInitProviderWithSingleRegistry() + mockInitProviderWithSingleRegistry() ms := &MockService{} SetConsumerService(ms) @@ -110,7 +112,7 @@ func TestLoadWithSingleReg(t *testing.T) { conServices = map[string]common.RPCService{} proServices = map[string]common.RPCService{} - common.ServiceMap.UnRegister("mock", "MockService") + common.ServiceMap.UnRegister("com.MockService", "mock", "MockService") consumerConfig = nil providerConfig = nil } @@ -139,7 +141,7 @@ func TestWithNoRegLoad(t *testing.T) { conServices = map[string]common.RPCService{} proServices = map[string]common.RPCService{} - common.ServiceMap.UnRegister("mock", "MockService") + common.ServiceMap.UnRegister("com.MockService", "mock", "MockService") consumerConfig = nil providerConfig = nil } @@ -232,3 +234,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 debcd79fa281c40e5526f60f5c5cdb66688688f4..f8b671bf3cf8304d57211f2f8b7c7c35f2aa6b9e 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -38,24 +38,22 @@ import ( // consumerConfig ///////////////////////// -// ConsumerConfig ... +// ConsumerConfig is Consumer default configuration 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 `yaml:"registries" json:"registries,omitempty" 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" ` @@ -63,7 +61,7 @@ type ConsumerConfig struct { ConfigType map[string]string `yaml:"config_type" json:"config_type,omitempty" property:"config_type"` } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the ConsumerConfig by @unmarshal function func (c *ConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -75,17 +73,17 @@ func (c *ConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } -// Prefix ... +// nolint func (*ConsumerConfig) Prefix() string { return constant.ConsumerConfigPrefix } -// SetConsumerConfig ... +// SetConsumerConfig sets consumerConfig by @c func SetConsumerConfig(c ConsumerConfig) { consumerConfig = &c } -// ConsumerInit ... +// ConsumerInit loads config file to init consumer config func ConsumerInit(confConFile string) error { if confConFile == "" { return perrors.Errorf("application configure(consumer) file name is nil") @@ -129,7 +127,7 @@ func configCenterRefreshConsumer() error { var err error if consumerConfig.ConfigCenterConfig != nil { consumerConfig.SetFatherConfig(consumerConfig) - if err := consumerConfig.startConfigCenter(); err != nil { + if err = consumerConfig.startConfigCenter(); err != nil { return perrors.Errorf("start config center error , error message is {%v}", perrors.WithStack(err)) } consumerConfig.fresh() @@ -144,6 +142,5 @@ func configCenterRefreshConsumer() error { return perrors.WithMessagef(err, "time.ParseDuration(Connect_Timeout{%#v})", consumerConfig.Connect_Timeout) } } - return nil } diff --git a/config/generic_service.go b/config/generic_service.go index b66e399f9e5f467e51c8eccf465f926ac44299d5..a3332afe04de1c9b32db42d18ed2590e4188be35 100644 --- a/config/generic_service.go +++ b/config/generic_service.go @@ -19,18 +19,18 @@ package config import "context" -// GenericService ... +// GenericService uses for generic invoke for service call type GenericService struct { Invoke func(ctx context.Context, req []interface{}) (interface{}, error) `dubbo:"$invoke"` referenceStr string } -// NewGenericService ... +// NewGenericService returns a GenericService instance func NewGenericService(referenceStr string) *GenericService { return &GenericService{referenceStr: referenceStr} } -// Reference ... +// Reference gets referenceStr from GenericService func (u *GenericService) Reference() string { return u.referenceStr } diff --git a/config/graceful_shutdown.go b/config/graceful_shutdown.go index 382f05c8d57c4363108873433fd03565d03b9a50..aa102f35e9048dbc6fbcb10db19cb802b2f3147b 100644 --- a/config/graceful_shutdown.go +++ b/config/graceful_shutdown.go @@ -52,7 +52,7 @@ import ( * We define them by using 'package build' feature https://golang.org/pkg/go/build/ */ -// GracefulShutdownInit ... +// nolint func GracefulShutdownInit() { signals := make(chan os.Signal, 1) @@ -83,7 +83,7 @@ func GracefulShutdownInit() { }() } -// BeforeShutdown ... +// BeforeShutdown provides processing flow before shutdown func BeforeShutdown() { destroyAllRegistries() @@ -126,10 +126,8 @@ func destroyConsumerProtocols(consumerProtocols *gxset.HashSet) { } } -/** - * destroy the provider's protocol. - * if the protocol is consumer's protocol too, we will keep it. - */ +// destroyProviderProtocols destroys the provider's protocol. +// if the protocol is consumer's protocol too, we will keep it func destroyProviderProtocols(consumerProtocols *gxset.HashSet) { logger.Info("Graceful shutdown --- Destroy provider's protocols. ") @@ -215,9 +213,7 @@ func totalTimeout() time.Duration { return timeout } -/* - * we can not get the protocols from consumerConfig because some protocol don't have configuration, like jsonrpc. - */ +// we can not get the protocols from consumerConfig because some protocol don't have configuration, like jsonrpc. func getConsumerProtocols() *gxset.HashSet { result := gxset.NewSet() if consumerConfig == nil || consumerConfig.References == nil { diff --git a/config/graceful_shutdown_config.go b/config/graceful_shutdown_config.go index 6bbabebf2538effcbbe4bddc50857acf5f962a61..87175166b7011e27985eac404819a1aba3522e49 100644 --- a/config/graceful_shutdown_config.go +++ b/config/graceful_shutdown_config.go @@ -31,7 +31,7 @@ const ( defaultStepTimeout = 10 * time.Second ) -// ShutdownConfig ... +// ShutdownConfig is used as configuration for graceful shutdown type ShutdownConfig struct { /* * Total timeout. Even though we don't release all resources, @@ -58,12 +58,12 @@ type ShutdownConfig struct { RequestsFinished bool } -// Prefix ... +// nolint func (config *ShutdownConfig) Prefix() string { return constant.ShutdownConfigPrefix } -// GetTimeout ... +// nolint func (config *ShutdownConfig) GetTimeout() time.Duration { result, err := time.ParseDuration(config.Timeout) if err != nil { @@ -74,7 +74,7 @@ func (config *ShutdownConfig) GetTimeout() time.Duration { return result } -// GetStepTimeout ... +// nolint func (config *ShutdownConfig) GetStepTimeout() time.Duration { result, err := time.ParseDuration(config.StepTimeout) if err != nil { diff --git a/config/graceful_shutdown_signal_darwin.go b/config/graceful_shutdown_signal_darwin.go index 8ad79ffa62ceed4096c60bfb9139b7ff1586808e..6f1fa982a30125096c553e65c13bae1a413ea141 100644 --- a/config/graceful_shutdown_signal_darwin.go +++ b/config/graceful_shutdown_signal_darwin.go @@ -23,12 +23,12 @@ import ( ) var ( - // ShutdownSignals ... + // 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} - // DumpHeapShutdownSignals ... + // DumpHeapShutdownSignals receives shutdown signals to process DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, syscall.SIGABRT, syscall.SIGSYS} ) diff --git a/config/graceful_shutdown_signal_linux.go b/config/graceful_shutdown_signal_linux.go index 8ad79ffa62ceed4096c60bfb9139b7ff1586808e..6f1fa982a30125096c553e65c13bae1a413ea141 100644 --- a/config/graceful_shutdown_signal_linux.go +++ b/config/graceful_shutdown_signal_linux.go @@ -23,12 +23,12 @@ import ( ) var ( - // ShutdownSignals ... + // 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} - // DumpHeapShutdownSignals ... + // DumpHeapShutdownSignals receives shutdown signals to process DumpHeapShutdownSignals = []os.Signal{syscall.SIGQUIT, syscall.SIGILL, syscall.SIGTRAP, syscall.SIGABRT, syscall.SIGSYS} ) diff --git a/config/graceful_shutdown_signal_windows.go b/config/graceful_shutdown_signal_windows.go index 815a05ecb20a8fc202debaf6f39d699845cd689e..3136e5ae15081f026e8a6e602a5174e1d396abf7 100644 --- a/config/graceful_shutdown_signal_windows.go +++ b/config/graceful_shutdown_signal_windows.go @@ -23,11 +23,11 @@ import ( ) var ( - // ShutdownSignals ... + // 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} - // DumpHeapShutdownSignals ... + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..8e833dd70bcc0db8e65cd8703f2bc1859432a887 --- /dev/null +++ b/config/instance/metadata_report.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 instance + +import ( + "sync" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/metadata/report" +) + +var ( + instance report.MetadataReport + reportUrl common.URL + once sync.Once +) + +// 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() { + 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..51780a53f6d7e80e5c7d7a15917aee629b000e1c --- /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 { + panic("implement me") +} + +func (m mockMetadataReport) SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, []common.URL) error { + panic("implement me") +} + +func (m mockMetadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) []string { + panic("implement me") +} + +func (m mockMetadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) string { + panic("implement me") +} diff --git a/config/interfaces/config_reader.go b/config/interfaces/config_reader.go index 23f2225e1bd670d43065f3b6eca08385a5c964a2..b23f989cc29b42de349d1d80ae594de32ab4abbd 100644 --- a/config/interfaces/config_reader.go +++ b/config/interfaces/config_reader.go @@ -1,8 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package interfaces import "bytes" -// ConfigReader +// ConfigReader is used to read config from consumer or provider type ConfigReader interface { ReadConsumerConfig(reader *bytes.Buffer) error ReadProviderConfig(reader *bytes.Buffer) error diff --git a/config/metadata_report_config.go b/config/metadata_report_config.go index 41fb6b4769e59784d8d18c3f82b956fd029d4ff7..6d319e5ecb8007e06dcf790fff145bfab754df3d 100644 --- a/config/metadata_report_config.go +++ b/config/metadata_report_config.go @@ -32,23 +32,20 @@ import ( "github.com/apache/dubbo-go/config/instance" ) -// MethodConfig ... +// 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"` } -// Prefix ... +// nolint func (c *MetadataReportConfig) Prefix() string { return constant.MetadataReportPrefix } -// UnmarshalYAML ... +// UnmarshalYAML unmarshal the MetadataReportConfig by @unmarshal function func (c *MetadataReportConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return perrors.WithStack(err) @@ -60,7 +57,7 @@ func (c *MetadataReportConfig) UnmarshalYAML(unmarshal func(interface{}) error) return nil } -// ToUrl ... +// nolint func (c *MetadataReportConfig) ToUrl() (*common.URL, error) { urlMap := make(url.Values) @@ -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,20 +93,18 @@ 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 { instance.GetMetadataReportInstance(url) } else { - return perrors.New("MetadataConfig is invalid!") + return perrors.Wrap(err, "Start MetadataReport failed.") } return nil diff --git a/config/metadata_report_config_test.go b/config/metadata_report_config_test.go index d6b08d5fb0c51495940d4dc021a0796c1d577923..1c585ee79d58826b227df52574b3403639856306 100644 --- a/config/metadata_report_config_test.go +++ b/config/metadata_report_config_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package config import "testing" @@ -7,12 +24,15 @@ import ( ) func TestMetadataReportConfig_ToUrl(t *testing.T) { - metadataReportConfig := MetadataReportConfig{ - Protocol: "mock", + GetBaseConfig().Remotes["mock"] = &RemoteConfig{ Address: "127.0.0.1:2181", Username: "test", Password: "test", TimeoutStr: "3s", + } + metadataReportConfig := MetadataReportConfig{ + Protocol: "mock", + RemoteRef: "mock", Params: map[string]string{ "k": "v", }, diff --git a/config/method_config.go b/config/method_config.go index 8f196d9e2c03071a663db03cb185fb9106d6484a..e64773eb135b2f9ec55377bded815147e2e192af 100644 --- a/config/method_config.go +++ b/config/method_config.go @@ -42,7 +42,7 @@ type MethodConfig struct { RequestTimeout string `yaml:"timeout" json:"timeout,omitempty" property:"timeout"` } -// Prefix ... +// nolint func (c *MethodConfig) Prefix() string { if len(c.InterfaceId) != 0 { return constant.DUBBO + "." + c.InterfaceName + "." + c.InterfaceId + "." + c.Name + "." @@ -51,7 +51,7 @@ func (c *MethodConfig) Prefix() string { return constant.DUBBO + "." + c.InterfaceName + "." + c.Name + "." } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the MethodConfig by @unmarshal function func (c *MethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err diff --git a/config/mock_rpcservice.go b/config/mock_rpcservice.go index 6c43699128247bf0ec483eb83f879bf4c3b67a37..1e21b252f5cf094c3bd103e3624fbcb2e2b1e2b1 100644 --- a/config/mock_rpcservice.go +++ b/config/mock_rpcservice.go @@ -21,20 +21,20 @@ import ( "context" ) -// MockService ... +// MockService mocks the rpc service for test type MockService struct{} -// Reference ... +// Reference mocks the Reference method func (*MockService) Reference() string { return "MockService" } -// GetUser ... +// GetUser mocks the GetUser method func (*MockService) GetUser(ctx context.Context, itf []interface{}, str *struct{}) error { return nil } -// GetUser1 ... +// GetUser1 mocks the GetUser1 method func (*MockService) GetUser1(ctx context.Context, itf []interface{}, str *struct{}) error { return nil } diff --git a/config/protocol_config.go b/config/protocol_config.go index 33de976bc6f5bf7341ddcff8d51c505cf23bbccd..cee5b7aa7518ff55a15d05de4733cefbbc9c0a1c 100644 --- a/config/protocol_config.go +++ b/config/protocol_config.go @@ -25,14 +25,14 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -// ProtocolConfig ... +// ProtocolConfig is protocol configuration type ProtocolConfig struct { Name string `required:"true" yaml:"name" json:"name,omitempty" property:"name"` Ip string `required:"true" yaml:"ip" json:"ip,omitempty" property:"ip"` Port string `required:"true" yaml:"port" json:"port,omitempty" property:"port"` } -// Prefix ... +// nolint func (c *ProtocolConfig) Prefix() string { return constant.ProtocolConfigPrefix } diff --git a/config/provider_config.go b/config/provider_config.go index 79569917455773653750d1d5921a722daf079b0a..9d8a2429d2fc1d88b01c436ae96d55bcd1c729aa 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" ) @@ -36,26 +35,23 @@ import ( // providerConfig ///////////////////////// -// ProviderConfig ... +// 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 `yaml:"registries" json:"registries,omitempty" property:"registries"` } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the ProviderConfig by @unmarshal function func (c *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -67,17 +63,17 @@ func (c *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } -// Prefix ... +// nolint func (*ProviderConfig) Prefix() string { return constant.ProviderConfigPrefix } -// SetProviderConfig ... +// SetProviderConfig sets provider config by @p func SetProviderConfig(p ProviderConfig) { providerConfig = &p } -// ProviderInit ... +// ProviderInit loads config file to init provider config func ProviderInit(confProFile string) error { if len(confProFile) == 0 { return perrors.Errorf("application configure(provider) file name is nil") @@ -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 7ce0013194f5c1a1d09e014a858433833aa07f0e..748b2d403fe315f879c817c4e0a4cf3197d807da 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -39,7 +39,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// ReferenceConfig ... +// ReferenceConfig is the configuration of service consumer type ReferenceConfig struct { context context.Context pxy *proxy.Proxy @@ -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"` @@ -63,9 +64,10 @@ type ReferenceConfig struct { Generic bool `yaml:"generic" json:"generic,omitempty" property:"generic"` Sticky bool `yaml:"sticky" json:"sticky,omitempty" property:"sticky"` RequestTimeout string `yaml:"timeout" json:"timeout,omitempty" property:"timeout"` + ForceTag bool `yaml:"force.tag" json:"force.tag,omitempty" property:"force.tag"` } -// Prefix ... +// nolint func (c *ReferenceConfig) Prefix() string { return constant.ReferenceConfigPrefix + c.InterfaceName + "." } @@ -75,7 +77,7 @@ func NewReferenceConfig(id string, ctx context.Context) *ReferenceConfig { return &ReferenceConfig{id: id, context: ctx} } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the ReferenceConfig by @unmarshal function func (c *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { type rf ReferenceConfig raw := rf{} // Put your defaults here @@ -99,7 +101,9 @@ func (c *ReferenceConfig) Refer(_ interface{}) { common.WithParams(c.getUrlMap()), common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), ) - + if c.ForceTag { + cfgURL.AddParam(constant.ForceUseTag, "true") + } if c.Url != "" { // 1. user specified URL, could be peer-to-peer address, or register center's address. urlStrings := gxstrings.RegSplit(c.Url, "\\s*[;]+\\s*") @@ -141,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)) @@ -165,7 +171,7 @@ func (c *ReferenceConfig) Implement(v common.RPCService) { c.pxy.Implement(v) } -// GetRPCService ... +// GetRPCService gets RPCService from proxy func (c *ReferenceConfig) GetRPCService() common.RPCService { return c.pxy.Get() } @@ -185,6 +191,11 @@ 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()) + if len(c.RequestTimeout) != 0 { urlMap.Set(constant.TIMEOUT_KEY, c.RequestTimeout) } diff --git a/config/reference_config_test.go b/config/reference_config_test.go index 7a65e55f09c997cb49b83f1f185faf9338cf0f5a..05b386f75e2b1fc062783e1de224a49329fda1fb 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", @@ -135,19 +139,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/registry_config.go b/config/registry_config.go index f3d22311b86d4cc3b66f12e9926dff9565ae4cd6..ef527c827e9dac4cd2762f579d30254e9e51150f 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -33,20 +33,21 @@ import ( "github.com/apache/dubbo-go/common/logger" ) -// RegistryConfig ... +// RegistryConfig is the configuration of the registry center type RegistryConfig struct { Protocol string `required:"true" yaml:"protocol" json:"protocol,omitempty" property:"protocol"` - //I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig + // I changed "type" to "protocol" ,the same as "protocol" field in java class RegistryConfig TimeoutStr string `yaml:"timeout" default:"5s" json:"timeout,omitempty" property:"timeout"` // unit: second Group string `yaml:"group" json:"group,omitempty" property:"group"` - //for registry - 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"` + // for registry + 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"` + Simplified bool `yaml:"simplified" json:"simplified,omitempty" property:"simplified"` + Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the RegistryConfig by @unmarshal function func (c *RegistryConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -58,7 +59,7 @@ func (c *RegistryConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return nil } -// Prefix ... +// nolint func (*RegistryConfig) Prefix() string { return constant.RegistryConfigPrefix + "|" + constant.SingleRegistryConfigPrefix } @@ -70,9 +71,11 @@ func loadRegistries(targetRegistries string, registries map[string]*RegistryConf for k, registryConf := range registries { target := false - // if user not config targetRegistries,default load all - // Notice:in func "func Split(s, sep string) []string" comment : if s does not contain sep and sep is not empty, SplitAfter returns a slice of length 1 whose only element is s. - // So we have to add the condition when targetRegistries string is not set (it will be "" when not set) + // if user not config targetRegistries, default load all + // Notice: in func "func Split(s, sep string) []string" comment: + // if s does not contain sep and sep is not empty, SplitAfter returns + // a slice of length 1 whose only element is s. So we have to add the + // condition when targetRegistries string is not set (it will be "" when not set) if len(trSlice) == 0 || (len(trSlice) == 1 && trSlice[0] == "") { target = true } else { @@ -86,29 +89,24 @@ func loadRegistries(targetRegistries string, registries map[string]*RegistryConf } if target { - var ( - url common.URL - err error - ) - addresses := strings.Split(registryConf.Address, ",") address := addresses[0] address = translateRegistryConf(address, registryConf) - url, err = common.NewURL(constant.REGISTRY_PROTOCOL+"://"+address, + url, err := common.NewURL(constant.REGISTRY_PROTOCOL+"://"+address, common.WithParams(registryConf.getUrlMap(roleType)), + common.WithParamsValue("simplified", strconv.FormatBool(registryConf.Simplified)), common.WithUsername(registryConf.Username), common.WithPassword(registryConf.Password), common.WithLocation(registryConf.Address), ) if err != nil { - logger.Errorf("The registry id:%s url is invalid , error: %#v", k, err) + logger.Errorf("The registry id: %s url is invalid, error: %#v", k, err) panic(err) } else { urls = append(urls, &url) } } - } return urls @@ -123,7 +121,6 @@ func (c *RegistryConfig) getUrlMap(roleType common.RoleType) url.Values { for k, v := range c.Params { urlMap.Set(k, v) } - return urlMap } @@ -131,7 +128,7 @@ func translateRegistryConf(address string, registryConf *RegistryConfig) string if strings.Contains(address, "://") { translatedUrl, err := url.Parse(address) if err != nil { - logger.Errorf("The registry url is invalid , error: %#v", err) + logger.Errorf("The registry url is invalid, error: %#v", err) panic(err) } address = translatedUrl.Host 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/condition_router_config.go b/config/router_config.go similarity index 80% rename from config/condition_router_config.go rename to config/router_config.go index 87e835108efd2ccac4f829386ec44a3916339f85..16943d96be76f93c2d540e2ccf16670b7424298f 100644 --- a/config/condition_router_config.go +++ b/config/router_config.go @@ -18,31 +18,40 @@ 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" ) -//RouterInit Load config file to init router config +var ( + routerURLSet = gxset.NewSet() +) + +// RouterInit Load config file to init router config func RouterInit(confRouterFile string) error { fileRouterFactories := extension.GetFileRouterFactories() bytes, err := yaml.LoadYMLConfig(confRouterFile) if err != nil { return perrors.Errorf("ioutil.ReadFile(file:%s) = error:%v", confRouterFile, perrors.WithStack(err)) } + logger.Warnf("get fileRouterFactories len{%+v})", len(fileRouterFactories)) for k, factory := range fileRouterFactories { 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 \n", k) + 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/condition_router_config_test.go b/config/router_config_test.go similarity index 81% rename from config/condition_router_config_test.go rename to config/router_config_test.go index 2f0a38b2fdf59578c77076680c05b3eca5c26a1c..bf189b600f1135e4059c8833a3de042bba5427ff 100644 --- a/config/condition_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" ) @@ -53,15 +52,3 @@ func TestString(t *testing.T) { assert.Equal(t, n2[0], "a1") assert.Equal(t, n2[1], "") } - -func TestRouterInit(t *testing.T) { - errPro := RouterInit(errorTestYML) - assert.Error(t, errPro) - - assert.Equal(t, 0, directory.GetRouterURLSet().Size()) - - errPro = RouterInit(testYML) - assert.NoError(t, errPro) - - assert.Equal(t, 1, directory.GetRouterURLSet().Size()) -} diff --git a/config/service.go b/config/service.go index b7e7dc2a425b42363d570fc37a70e2e5094e7d9d..b746141dd0d55f8e306d9f4d13de2b9021c70272 100644 --- a/config/service.go +++ b/config/service.go @@ -36,17 +36,17 @@ func SetProviderService(service common.RPCService) { proServices[service.Reference()] = service } -// GetConsumerService ... +// GetConsumerService gets ConsumerService by @name func GetConsumerService(name string) common.RPCService { return conServices[name] } -// GetProviderService ... +// GetProviderService gets ProviderService by @name func GetProviderService(name string) common.RPCService { return proServices[name] } -// GetCallback ... +// GetCallback gets CallbackResponse by @name func GetCallback(name string) func(response common.CallbackResponse) { service := GetConsumerService(name) if sv, ok := service.(common.AsyncCallbackService); ok { diff --git a/config/service_config.go b/config/service_config.go index 7d97fa4d1e95bd79e051f77deaeafa1afcc58b0f..a500a44419f99fccc6f0dc18814e38ed005cb737 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -18,6 +18,7 @@ package config import ( + "container/list" "context" "fmt" "net/url" @@ -29,6 +30,7 @@ import ( import ( "github.com/creasty/defaults" + gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" ) @@ -42,7 +44,7 @@ import ( "github.com/apache/dubbo-go/protocol/protocolwrapper" ) -// ServiceConfig ... +// ServiceConfig is the configuration of the service provider type ServiceConfig struct { context context.Context id string @@ -69,20 +71,25 @@ type ServiceConfig struct { ExecuteLimitRejectedHandler string `yaml:"execute.limit.rejected.handler" json:"execute.limit.rejected.handler,omitempty" property:"execute.limit.rejected.handler"` Auth string `yaml:"auth" json:"auth,omitempty" property:"auth"` ParamSign string `yaml:"param.sign" json:"param.sign,omitempty" property:"param.sign"` + 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 } -// Prefix ... +// Prefix return dubbo.service.${interface}. func (c *ServiceConfig) Prefix() string { return constant.ServiceConfigPrefix + c.InterfaceName + "." } -// UnmarshalYAML ... +// UnmarshalYAML unmarshals the ServiceConfig by @unmarshal function func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := defaults.Set(c); err != nil { return err @@ -91,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 } @@ -104,7 +113,35 @@ func NewServiceConfig(id string, context context.Context) *ServiceConfig { } } -// Export ... +// 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() + for _, proto := range protocolConfigs { + if len(proto.Port) > 0 { + continue + } + + tcp, err := gxnet.ListenOnTCPRandomPort(proto.Ip) + if err != nil { + panic(perrors.New(fmt.Sprintf("Get tcp port error,err is {%v}", err))) + } + defer tcp.Close() + ports.PushBack(strings.Split(tcp.Addr().String(), ":")[1]) + } + return ports +} + +// Export export the service func (c *ServiceConfig) Export() error { // TODO: config center start here @@ -121,29 +158,44 @@ 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 } + + ports := getRandomPort(protocolConfigs) + nextPort := ports.Front() for _, proto := range protocolConfigs { // registry the service reflect - methods, err := common.ServiceMap.Register(proto.Name, c.rpcService) + methods, err := common.ServiceMap.Register(c.InterfaceName, proto.Name, c.rpcService) if err != nil { - err := perrors.Errorf("The service %v export the protocol %v error! Error message is %v .", c.InterfaceName, proto.Name, err.Error()) - logger.Errorf(err.Error()) - return err + formatErr := perrors.Errorf("The service %v export the protocol %v error! Error message is %v .", c.InterfaceName, proto.Name, err.Error()) + logger.Errorf(formatErr.Error()) + return formatErr + } + + port := proto.Port + + if len(proto.Port) == 0 { + port = nextPort.Value.(string) + nextPort = nextPort.Next() } ivkURL := common.NewURLWithOptions( common.WithPath(c.id), common.WithProtocol(proto.Name), common.WithIp(proto.Ip), - common.WithPort(proto.Port), + common.WithPort(port), common.WithParams(urlMap), common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), common.WithMethods(strings.Split(methods, ",")), common.WithToken(c.Token), ) + if len(c.Tag) > 0 { + ivkURL.AddParam(constant.Tagkey, c.Tag) + } + + var exporter protocol.Exporter if len(regUrls) > 0 { for _, regUrl := range regUrls { @@ -157,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 } @@ -193,6 +269,9 @@ func (c *ServiceConfig) getUrlMap() url.Values { urlMap.Set(constant.GROUP_KEY, c.Group) urlMap.Set(constant.VERSION_KEY, c.Version) urlMap.Set(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER)) + urlMap.Set(constant.RELEASE_KEY, "dubbo-golang-"+constant.Version) + urlMap.Set(constant.SIDE_KEY, (common.RoleType(common.PROVIDER)).Role()) + // application info urlMap.Set(constant.APPLICATION_KEY, providerConfig.ApplicationConfig.Name) urlMap.Set(constant.ORGANIZATION_KEY, providerConfig.ApplicationConfig.Organization) @@ -239,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 6f3230890348e77ea26c9c0eaf9165090c8cd09f..7630d5845e6770ec269bb2e07876bc7ba0d18329 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -21,52 +21,26 @@ import ( "testing" ) +import ( + gxnet "github.com/dubbogo/gost/net" + "github.com/stretchr/testify/assert" + "go.uber.org/atomic" +) + import ( "github.com/apache/dubbo-go/common/extension" ) 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": { @@ -92,6 +66,7 @@ func doInitProvider() { Weight: 200, }, }, + exported: new(atomic.Bool), }, "MockServiceNoRightProtocol": { InterfaceName: "com.MockService", @@ -116,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", @@ -185,7 +147,40 @@ func Test_Export(t *testing.T) { for i := range providerConfig.Services { service := providerConfig.Services[i] service.Implement(&MockService{}) + service.Protocols = providerConfig.Protocols service.Export() } providerConfig = nil } + +func Test_getRandomPort(t *testing.T) { + protocolConfigs := make([]*ProtocolConfig, 0, 3) + + ip, err := gxnet.GetLocalIP() + protocolConfigs = append(protocolConfigs, &ProtocolConfig{ + Ip: ip, + }) + protocolConfigs = append(protocolConfigs, &ProtocolConfig{ + Ip: ip, + }) + protocolConfigs = append(protocolConfigs, &ProtocolConfig{ + Ip: ip, + }) + assert.NoError(t, err) + ports := getRandomPort(protocolConfigs) + + assert.Equal(t, ports.Len(), len(protocolConfigs)) + + front := ports.Front() + for { + if front == nil { + break + } + t.Logf("port:%v", front.Value) + front = front.Next() + } + + protocolConfigs = make([]*ProtocolConfig, 0, 3) + ports = getRandomPort(protocolConfigs) + assert.Equal(t, ports.Len(), len(protocolConfigs)) +} diff --git a/config/service_discovery_config.go b/config/service_discovery_config.go new file mode 100644 index 0000000000000000000000000000000000000000..d3dc697a9b31e6949375d74aa92234093da22378 --- /dev/null +++ b/config/service_discovery_config.go @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package config + +// ServiceDiscoveryConfig will be used to create +type ServiceDiscoveryConfig struct { + // Protocol indicate which implementation will be used. + // for example, if the Protocol is nacos, it means that we will use nacosServiceDiscovery + Protocol string `yaml:"protocol" json:"protocol,omitempty"` + // Group, usually you don't need to config this field. + // you can use this to do some isolation + Group string `yaml:"group" json:"group,omitempty"` + // RemoteRef is the reference point to RemoteConfig which will be used to create remotes instances. + RemoteRef string `yaml:"remote_ref" json:"remote_ref,omitempty"` +} diff --git a/config_center/apollo/factory.go b/config_center/apollo/factory.go index a5a69e121598bea4194398423775a99f04b61ced..f975ce13d8e5832f03051c42f92157532347d283 100644 --- a/config_center/apollo/factory.go +++ b/config_center/apollo/factory.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package apollo diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go index 9045c2864933fcfc79d5be32d160324805605595..b049d334bca7e5191caaf9674734e731bc709ba2 100644 --- a/config_center/apollo/impl.go +++ b/config_center/apollo/impl.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package apollo diff --git a/config_center/apollo/listener.go b/config_center/apollo/listener.go index 820d02fb48e2204c3f1eb74fd5624132a63d367e..1cf65ed22ba0a1f765af66191ed19a04f81b0fe6 100644 --- a/config_center/apollo/listener.go +++ b/config_center/apollo/listener.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package apollo @@ -29,7 +29,7 @@ type apolloListener struct { listeners map[config_center.ConfigurationListener]struct{} } -// NewApolloListener ... +// NewApolloListener creates a new apolloListener func NewApolloListener() *apolloListener { return &apolloListener{ listeners: make(map[config_center.ConfigurationListener]struct{}, 0), @@ -49,7 +49,7 @@ func (a *apolloListener) OnChange(changeEvent *agollo.ChangeEvent) { } } -// AddListener ... +// AddListener adds a listener for apollo func (a *apolloListener) AddListener(l config_center.ConfigurationListener) { if _, ok := a.listeners[l]; !ok { a.listeners[l] = struct{}{} @@ -57,7 +57,7 @@ func (a *apolloListener) AddListener(l config_center.ConfigurationListener) { } } -// RemoveListener ... +// RemoveListener removes listeners of apollo func (a *apolloListener) RemoveListener(l config_center.ConfigurationListener) { delete(a.listeners, l) } diff --git a/config_center/configuration_listener.go b/config_center/configuration_listener.go index e70e4f68075c51c33f1110ef44a7b703e36fb78d..541cc09286bb83fa5b66db3745e45ad0a9df5e2f 100644 --- a/config_center/configuration_listener.go +++ b/config_center/configuration_listener.go @@ -25,12 +25,12 @@ import ( "github.com/apache/dubbo-go/remoting" ) -// ConfigurationListener ... +// ConfigurationListener for changing listener's event type ConfigurationListener interface { Process(*ConfigChangeEvent) } -// ConfigChangeEvent ... +// ConfigChangeEvent for changing listener's event type ConfigChangeEvent struct { Key string Value interface{} diff --git a/config_center/configurator.go b/config_center/configurator.go index ffa9034e05c4c3d4cc254886e2ed19576f155dec..9db4804365689d8eb357965973a2916e86383cf8 100644 --- a/config_center/configurator.go +++ b/config_center/configurator.go @@ -21,7 +21,7 @@ import ( "github.com/apache/dubbo-go/common" ) -// Configurator ... +// Configurator supports GetUrl and constructor type Configurator interface { GetUrl() *common.URL Configure(url *common.URL) diff --git a/config_center/configurator/mock.go b/config_center/configurator/mock.go index d294b9195db9cfe60056bc29ec26816f740ea396..7ec7179634cfd967cd27e85ed248e2075c387cb5 100644 --- a/config_center/configurator/mock.go +++ b/config_center/configurator/mock.go @@ -23,7 +23,7 @@ import ( "github.com/apache/dubbo-go/config_center" ) -// NewMockConfigurator ... +// NewMockConfigurator creates a new mockConfigurator func NewMockConfigurator(url *common.URL) config_center.Configurator { return &mockConfigurator{configuratorUrl: url} } @@ -32,12 +32,12 @@ type mockConfigurator struct { configuratorUrl *common.URL } -// GetUrl ... +// GetUrl gets a configuratorUrl func (c *mockConfigurator) GetUrl() *common.URL { return c.configuratorUrl } -// Configure ... +// Configure sets up param CLUSTER_KEY and cluster for url func (c *mockConfigurator) Configure(url *common.URL) { if cluster := c.GetUrl().GetParam(constant.CLUSTER_KEY, ""); cluster != "" { url.SetParam(constant.CLUSTER_KEY, cluster) diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go index 18415bee3a28b37ffc2f3f73cc7309b685de5408..ebd3dc601b2821f3f4e1e4405720e4ebc55b607e 100644 --- a/config_center/configurator/override.go +++ b/config_center/configurator/override.go @@ -50,12 +50,12 @@ func (c *overrideConfigurator) GetUrl() *common.URL { } func (c *overrideConfigurator) Configure(url *common.URL) { - //remove configuratorUrl some param that can not be configured + // remove configuratorUrl some param that can not be configured if c.configuratorUrl.GetParam(constant.ENABLED_KEY, "true") == "false" || len(c.configuratorUrl.Location) == 0 { return } - //branch for version 2.7.x + // branch for version 2.7.x apiVersion := c.configuratorUrl.GetParam(constant.CONFIG_VERSION_KEY, "") if len(apiVersion) != 0 { currentSide := url.GetParam(constant.SIDE_KEY, "") @@ -67,12 +67,12 @@ func (c *overrideConfigurator) Configure(url *common.URL) { c.configureIfMatch(url.Ip, url) } } else { - //branch for version 2.6.x and less + // branch for version 2.6.x and less c.configureDeprecated(url) } } -//translate from java, compatible rules in java +// configureIfMatch translate from java, compatible rules in java func (c *overrideConfigurator) configureIfMatch(host string, url *common.URL) { if constant.ANYHOST_VALUE == c.configuratorUrl.Ip || host == c.configuratorUrl.Ip { providers := c.configuratorUrl.GetParam(constant.OVERRIDE_PROVIDERS_KEY, "") @@ -105,8 +105,7 @@ func (c *overrideConfigurator) configureIfMatch(host string, url *common.URL) { if returnUrl { return } - configUrl := c.configuratorUrl.Clone() - configUrl.RemoveParams(conditionKeys) + configUrl := c.configuratorUrl.CloneExceptParams(conditionKeys) url.SetParams(configUrl.GetParams()) } } diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go index 9013d7140e757520f2e8f048ce53a5ac2a13f982..540febc9d38e164afcc62538478df140b7d671c7 100644 --- a/config_center/dynamic_configuration.go +++ b/config_center/dynamic_configuration.go @@ -40,7 +40,7 @@ const ( DEFAULT_CONFIG_TIMEOUT = "10s" ) -// DynamicConfiguration ... +// DynamicConfiguration for modify listener and get properties file type DynamicConfiguration interface { Parser() parser.ConfigurationParser SetParser(parser.ConfigurationParser) @@ -71,14 +71,14 @@ type Options struct { // Option ... type Option func(*Options) -// WithGroup ... +// WithGroup assigns group to opt.Group func WithGroup(group string) Option { return func(opt *Options) { opt.Group = group } } -// WithTimeout ... +// WithTimeout assigns time to opt.Timeout func WithTimeout(time time.Duration) Option { return func(opt *Options) { opt.Timeout = time diff --git a/config_center/dynamic_configuration_factory.go b/config_center/dynamic_configuration_factory.go index 9f9b13227f6623a02b0261c46d8d1e43624005f8..46faf864443b7f8780584213b758f26395224956 100644 --- a/config_center/dynamic_configuration_factory.go +++ b/config_center/dynamic_configuration_factory.go @@ -21,7 +21,7 @@ import ( "github.com/apache/dubbo-go/common" ) -// DynamicConfigurationFactory ... +// DynamicConfigurationFactory gets the DynamicConfiguration type DynamicConfigurationFactory interface { GetDynamicConfiguration(*common.URL) (DynamicConfiguration, error) } diff --git a/config_center/dynamic_configuration_test.go b/config_center/dynamic_configuration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8b5a8d7cc5afea8559d563f82f5b2dd80c51488e --- /dev/null +++ b/config_center/dynamic_configuration_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 config_center + +import ( + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" +) + +func TestWithTimeout(t *testing.T) { + fa := WithTimeout(12 * time.Second) + opt := &Options{} + fa(opt) + assert.Equal(t, 12*time.Second, opt.Timeout) +} + +func TestGetRuleKey(t *testing.T) { + url, err := common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider?interface=test&group=groupA&version=0") + assert.NoError(t, err) + assert.Equal(t, "test:0:groupA", GetRuleKey(url)) +} diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go index 59c788b65bce2a4773975ea1a96a314649781832..9cfb9e6078be60fbe2072e8e293143e8b111df58 100644 --- a/config_center/mock_dynamic_config.go +++ b/config_center/mock_dynamic_config.go @@ -43,7 +43,7 @@ var ( dynamicConfiguration *MockDynamicConfiguration ) -// GetDynamicConfiguration ... +// GetDynamicConfiguration returns a DynamicConfiguration func (f *MockDynamicConfigurationFactory) GetDynamicConfiguration(_ *common.URL) (DynamicConfiguration, error) { var err error once.Do(func() { @@ -99,16 +99,16 @@ type MockDynamicConfiguration struct { listener map[string]ConfigurationListener } -// AddListener ... +// AddListener adds a listener for MockDynamicConfiguration func (c *MockDynamicConfiguration) AddListener(key string, listener ConfigurationListener, _ ...Option) { c.listener[key] = listener } -// RemoveListener ... +// RemoveListener removes the listener for MockDynamicConfiguration func (c *MockDynamicConfiguration) RemoveListener(_ string, _ ConfigurationListener, _ ...Option) { } -// GetConfig ... +// GetConfig returns content of MockDynamicConfiguration func (c *MockDynamicConfiguration) GetConfig(_ string, _ ...Option) (string, error) { return c.content, nil @@ -119,17 +119,17 @@ func (c *MockDynamicConfiguration) GetConfigs(key string, opts ...Option) (strin return c.GetConfig(key, opts...) } -// Parser ... +// Parser returns a parser of MockDynamicConfiguration func (c *MockDynamicConfiguration) Parser() parser.ConfigurationParser { return c.parser } -// SetParser ... +// SetParser sets parser of MockDynamicConfiguration func (c *MockDynamicConfiguration) SetParser(p parser.ConfigurationParser) { c.parser = p } -// GetProperties ... +// GetProperties gets content of MockDynamicConfiguration func (c *MockDynamicConfiguration) GetProperties(_ string, _ ...Option) (string, error) { return c.content, nil } @@ -139,7 +139,7 @@ func (c *MockDynamicConfiguration) GetInternalProperty(key string, opts ...Optio return c.GetProperties(key, opts...) } -// GetRule ... +// GetRule gets properties of MockDynamicConfiguration func (c *MockDynamicConfiguration) GetRule(key string, opts ...Option) (string, error) { return c.GetProperties(key, opts...) } diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index d3373e249bf99873dd3aa05b7488b0e7f38730ec..3b432819f43327888ade3da5303e445d6a2ef0fe 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -18,6 +18,7 @@ package nacos import ( + "path/filepath" "strconv" "strings" "sync" @@ -36,7 +37,8 @@ import ( "github.com/apache/dubbo-go/common/logger" ) -const logDir = "logs/nacos/log" +// Nacos Log dir, it can be override when creating client by config_center.log_dir +var logDir = filepath.Join("logs", "nacos", "log") // NacosClient Nacos client type NacosClient struct { @@ -87,6 +89,7 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { } url := container.GetUrl() + logDir = url.GetParam(constant.CONFIG_LOG_DIR_KEY, logDir) if container.NacosClient() == nil { //in dubbo ,every registry only connect one node ,so this is []string{r.Address} diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go index ef63eeff6ddf4e5cd6fa2ba7da7996b3dbed94ac..d5e351e2bbf7d78301926c8394d07183f18bb678 100644 --- a/config_center/nacos/client_test.go +++ b/config_center/nacos/client_test.go @@ -53,3 +53,62 @@ func Test_newNacosClient(t *testing.T) { <-c.client.Done() c.Destroy() } + +func Test_setNacosClient(t *testing.T) { + server := mockCommonNacosServer() + nacosURL := server.Listener.Addr().String() + registryUrl, _ := common.NewURL(nacosURL) + c := &nacosDynamicConfiguration{ + url: ®istryUrl, + done: make(chan struct{}), + } + var client *NacosClient + client = &NacosClient{ + name: nacosClientName, + NacosAddrs: []string{nacosURL}, + Timeout: 15 * time.Second, + exit: make(chan struct{}), + onceClose: func() { + close(client.exit) + }, + } + c.SetNacosClient(client) + err := ValidateNacosClient(c, WithNacosName(nacosClientName)) + assert.NoError(t, err) + c.wg.Add(1) + go HandleClientRestart(c) + go func() { + // c.client.Close() and <-c.client.Done() have order requirements. + // If c.client.Close() is called first.It is possible that "go HandleClientRestart(c)" + // sets c.client to nil before calling c.client.Done(). + time.Sleep(time.Second) + c.client.Close() + }() + <-c.client.Done() + c.Destroy() +} + +func Test_newNacosClient_connectError(t *testing.T) { + nacosURL := "registry://127.0.0.1:8888" + registryUrl, err := common.NewURL(nacosURL) + assert.NoError(t, err) + c := &nacosDynamicConfiguration{ + url: ®istryUrl, + done: make(chan struct{}), + } + err = ValidateNacosClient(c, WithNacosName(nacosClientName)) + assert.NoError(t, err) + c.wg.Add(1) + go HandleClientRestart(c) + go func() { + // c.client.Close() and <-c.client.Done() have order requirements. + // If c.client.Close() is called first.It is possible that "go HandleClientRestart(c)" + // sets c.client to nil before calling c.client.Done(). + time.Sleep(time.Second) + c.client.Close() + }() + <-c.client.Done() + // let client do retry + time.Sleep(5 * time.Second) + c.Destroy() +} 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 4032c91cda512b649140db6eea3dc11eeb482f27..03fc6772e70d623575575c8522508ae4fff12a04 100644 --- a/config_center/nacos/impl_test.go +++ b/config_center/nacos/impl_test.go @@ -72,12 +72,13 @@ func initNacosData(t *testing.T) (*nacosDynamicConfiguration, error) { server := mockCommonNacosServer() nacosURL := strings.ReplaceAll(server.URL, "http", "registry") regurl, _ := common.NewURL(nacosURL) - nacosConfiguration, err := newNacosDynamicConfiguration(®url) + factory := &nacosDynamicConfigurationFactory{} + nacosConfiguration, err := factory.GetDynamicConfiguration(®url) assert.NoError(t, err) nacosConfiguration.SetParser(&parser.DefaultConfigurationParser{}) - return nacosConfiguration, err + return nacosConfiguration.(*nacosDynamicConfiguration), err } func Test_GetConfig(t *testing.T) { @@ -88,6 +89,34 @@ func Test_GetConfig(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 TestNacosDynamicConfiguration_PublishConfig(t *testing.T) { nacos, err := initNacosData(t) assert.Nil(t, err) @@ -104,12 +133,10 @@ func Test_AddListener(t *testing.T) { listener := &mockDataListener{} time.Sleep(time.Second * 2) nacos.AddListener("dubbo.properties", listener) - listener.wg.Add(1) - listener.wg.Wait() } func Test_RemoveListener(t *testing.T) { - //TODO not supported in current go_nacos_sdk version + // TODO not supported in current go_nacos_sdk version } type mockDataListener struct { diff --git a/config_center/nacos/listener.go b/config_center/nacos/listener.go index de74cff8f64683a47278825b670352a04b69b791..fdf5a20d2ff4b97c1e0de40c8b5a0e573214fea4 100644 --- a/config_center/nacos/listener.go +++ b/config_center/nacos/listener.go @@ -46,7 +46,9 @@ func (n *nacosDynamicConfiguration) addListener(key string, listener config_cent go callback(listener, namespace, group, dataId, data) }, }) - logger.Errorf("nacos : listen config fail, error:%v ", err) + if err != nil { + logger.Errorf("nacos : listen config fail, error:%v ", err) + } newListener := make(map[config_center.ConfigurationListener]context.CancelFunc) newListener[listener] = cancel n.keyListeners.Store(key, newListener) diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index f33b4ba866da69e1d23b493f42152bbb0f437878..6fbdc27d4339150bfec624f7dc5ea0f6a608d7a7 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -47,7 +47,7 @@ type ConfigurationParser interface { ParseToUrls(content string) ([]*common.URL, error) } -// DefaultConfigurationParser for support properties file in config center +// DefaultConfigurationParser for supporting properties file in config center type DefaultConfigurationParser struct{} // ConfiguratorConfig ... @@ -71,7 +71,7 @@ type ConfigItem struct { Side string `yaml:"side"` } -// Parse ... +// Parse load content func (parser *DefaultConfigurationParser) Parse(content string) (map[string]string, error) { pps, err := properties.LoadString(content) if err != nil { diff --git a/config_center/parser/configuration_parser_test.go b/config_center/parser/configuration_parser_test.go index 7a59ea9b48b8b8d2a84735a416bcba1bb9ec8652..3ba10f73a4549181f37b89aedd4aedf4612bd7d4 100644 --- a/config_center/parser/configuration_parser_test.go +++ b/config_center/parser/configuration_parser_test.go @@ -32,3 +32,58 @@ func TestDefaultConfigurationParser_Parser(t *testing.T) { assert.Equal(t, 2, len(m)) assert.Equal(t, "172.0.0.1", m["dubbo.registry.address"]) } + +func TestDefaultConfigurationParser_appItemToUrls_ParserToUrls(t *testing.T) { + parser := &DefaultConfigurationParser{} + content := `configVersion: 2.7.1 +scope: application +key: org.apache.dubbo-go.mockService +enabled: true +configs: +- type: application + enabled: true + addresses: + - 0.0.0.0 + providerAddresses: [] + services: + - org.apache.dubbo-go.mockService + applications: [] + parameters: + cluster: mock1 + side: provider` + urls, err := parser.ParseToUrls(content) + assert.NoError(t, err) + assert.Equal(t, 1, len(urls)) + assert.Equal(t, "org.apache.dubbo-go.mockService", urls[0].GetParam("application", "")) + assert.Equal(t, "mock1", urls[0].GetParam("cluster", "")) + assert.Equal(t, "override", urls[0].Protocol) + assert.Equal(t, "0.0.0.0", urls[0].Location) +} + +func TestDefaultConfigurationParser_serviceItemToUrls_ParserToUrls(t *testing.T) { + parser := &DefaultConfigurationParser{} + content := `configVersion: 2.7.1 +scope: notApplication +key: groupA/test:1 +enabled: true +configs: +- type: application + enabled: true + addresses: + - 0.0.0.0 + providerAddresses: [] + services: + - org.apache.dubbo-go.mockService + applications: [] + parameters: + cluster: mock1 + side: provider` + urls, err := parser.ParseToUrls(content) + assert.NoError(t, err) + assert.Equal(t, 1, len(urls)) + assert.Equal(t, "groupA", urls[0].GetParam("group", "")) + assert.Equal(t, "/test", urls[0].Path) + assert.Equal(t, "mock1", urls[0].GetParam("cluster", "")) + assert.Equal(t, "override", urls[0].Protocol) + assert.Equal(t, "0.0.0.0", urls[0].Location) +} diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go index 937c56eef0b29f6fee52717f7a43c37ec35c7e95..ef579eb2d11cf5f5bafb132c3e201c12ee7845c0 100644 --- a/config_center/zookeeper/impl.go +++ b/config_center/zookeeper/impl.go @@ -20,11 +20,9 @@ package zookeeper import ( "strings" "sync" - "time" ) import ( - "github.com/dubbogo/go-zookeeper/zk" gxset "github.com/dubbogo/gost/container/set" perrors "github.com/pkg/errors" ) @@ -76,37 +74,11 @@ func newZookeeperDynamicConfiguration(url *common.URL) (*zookeeperDynamicConfigu c.cacheListener = NewCacheListener(c.rootPath) err = c.client.Create(c.rootPath) - c.listener.ListenServiceEvent(c.rootPath, c.cacheListener) + c.listener.ListenServiceEvent(url, c.rootPath, c.cacheListener) return c, err } -func newMockZookeeperDynamicConfiguration(url *common.URL, opts ...zookeeper.Option) (*zk.TestCluster, *zookeeperDynamicConfiguration, error) { - c := &zookeeperDynamicConfiguration{ - url: url, - rootPath: "/" + url.GetParam(constant.CONFIG_NAMESPACE_KEY, config_center.DEFAULT_GROUP) + "/config", - } - var ( - tc *zk.TestCluster - err error - ) - tc, c.client, _, err = zookeeper.NewMockZookeeperClient("test", 15*time.Second, opts...) - if err != nil { - logger.Errorf("mock zookeeper client start error ,error message is %v", err) - return tc, c, err - } - c.wg.Add(1) - go zookeeper.HandleClientRestart(c) - - c.listener = zookeeper.NewZkEventListener(c.client) - c.cacheListener = NewCacheListener(c.rootPath) - - err = c.client.Create(c.rootPath) - go c.listener.ListenServiceEvent(c.rootPath, c.cacheListener) - return tc, c, err - -} - func (c *zookeeperDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener, opions ...config_center.Option) { c.cacheListener.AddListener(key, listener) } diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go index 30389122a3a06ee260f2ed8b21057523137995d5..cfeba07a87e2534dc60e9ca2235550c1136bd978 100644 --- a/config_center/zookeeper/impl_test.go +++ b/config_center/zookeeper/impl_test.go @@ -18,6 +18,7 @@ package zookeeper import ( "fmt" + "strconv" "sync" "testing" ) @@ -30,16 +31,28 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/config_center" "github.com/apache/dubbo-go/config_center/parser" ) func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicConfiguration) { - regurl, _ := common.NewURL("registry://127.0.0.1:1111") - ts, reg, err := newMockZookeeperDynamicConfiguration(®url) - reg.SetParser(&parser.DefaultConfigurationParser{}) - + ts, err := zk.StartTestCluster(1, nil, nil) + assert.NoError(t, err) + assert.NotNil(t, ts.Servers[0]) + urlString := "registry://127.0.0.1:" + strconv.Itoa(ts.Servers[0].Port) + regurl, err := common.NewURL(urlString) + assert.NoError(t, err) + regurl.AddParam(constant.REGISTRY_TIMEOUT_KEY, "15s") + zkFactory := &zookeeperDynamicConfigurationFactory{} + reg, err := zkFactory.GetDynamicConfiguration(®url) + zreg, ok := reg.(*zookeeperDynamicConfiguration) + assert.True(t, ok) assert.NoError(t, err) + assert.True(t, zreg.IsAvailable()) + assert.Equal(t, zreg.GetUrl(), regurl) + assert.True(t, zreg.RestartCallBack()) + zreg.SetParser(&parser.DefaultConfigurationParser{}) data := ` dubbo.consumer.request_timeout=5s @@ -63,20 +76,20 @@ func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicC dubbo.service.com.ikurento.user.UserProvider.cluster=failover ` if group != "" { - err = reg.client.Create(reg.rootPath + "/dubbo/dubbo.properties") + err = zreg.client.Create(zreg.rootPath + "/dubbo/dubbo.properties") assert.NoError(t, err) - _, err = reg.client.Conn.Set(reg.rootPath+"/dubbo/dubbo.properties", []byte(data), 0) + _, err = zreg.client.Conn.Set(zreg.rootPath+"/dubbo/dubbo.properties", []byte(data), 0) assert.NoError(t, err) } else { - err = reg.client.Create(reg.rootPath + "/dubbo.properties") + err = zreg.client.Create(zreg.rootPath + "/dubbo.properties") assert.NoError(t, err) - _, err = reg.client.Conn.Set(reg.rootPath+"/dubbo.properties", []byte(data), 0) + _, err = zreg.client.Conn.Set(zreg.rootPath+"/dubbo.properties", []byte(data), 0) assert.NoError(t, err) } - return ts, reg + return ts, zreg } func Test_GetConfig(t *testing.T) { @@ -87,6 +100,12 @@ func Test_GetConfig(t *testing.T) { m, err := reg.Parser().Parse(configs) assert.NoError(t, err) assert.Equal(t, "5s", m["dubbo.consumer.request_timeout"]) + configs, err = reg.GetProperties("dubbo.properties") + assert.Error(t, err) + configs, err = reg.GetInternalProperty("dubbo.properties") + assert.Error(t, err) + configs, err = reg.GetRule("dubbo.properties") + assert.Error(t, err) } func Test_AddListener(t *testing.T) { diff --git a/config_center/zookeeper/listener.go b/config_center/zookeeper/listener.go index 122dfaf4f268a706151de6acdaa78bb46e59f8fb..747c4be352add3f549eaf02e83da6442a8a84c6a 100644 --- a/config_center/zookeeper/listener.go +++ b/config_center/zookeeper/listener.go @@ -33,12 +33,12 @@ type CacheListener struct { rootPath string } -// NewCacheListener ... +// NewCacheListener creates a new CacheListener func NewCacheListener(rootPath string) *CacheListener { return &CacheListener{rootPath: rootPath} } -// AddListener ... +// AddListener will add a listener if loaded func (l *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) { // reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure @@ -50,7 +50,7 @@ func (l *CacheListener) AddListener(key string, listener config_center.Configura } } -// RemoveListener ... +// RemoveListener will delete a listener if loaded func (l *CacheListener) RemoveListener(key string, listener config_center.ConfigurationListener) { listeners, loaded := l.keyListeners.Load(key) if loaded { @@ -58,7 +58,7 @@ func (l *CacheListener) RemoveListener(key string, listener config_center.Config } } -// DataChange ... +// DataChange changes all listeners' event func (l *CacheListener) DataChange(event remoting.Event) bool { if event.Content == "" { //meanings new node diff --git a/doc/pic/arch/dubbo-go-arch.png b/doc/pic/arch/dubbo-go-arch.png index 87726d88484c23d6395023bb10e86009d59a1fd7..e5f192715216257929cf4a550a1adf3588ab0e0f 100644 Binary files a/doc/pic/arch/dubbo-go-arch.png and b/doc/pic/arch/dubbo-go-arch.png differ diff --git a/doc/pic/arch/dubbo-go-ext.png b/doc/pic/arch/dubbo-go-ext.png new file mode 100644 index 0000000000000000000000000000000000000000..d065ef4d8e28e507b2ee36fd8c6c6928ab7c9b5d Binary files /dev/null and b/doc/pic/arch/dubbo-go-ext.png differ diff --git a/filter/access_key.go b/filter/access_key.go index 40d4157b31d13ed8fd8b1ba8cc9d16b53638ac6a..4801d64fe46461424c5dac5aef2eebc719ee19c4 100644 --- a/filter/access_key.go +++ b/filter/access_key.go @@ -22,6 +22,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// AccessKeyPair stores the basic attributes for authentication. type AccessKeyPair struct { AccessKey string `yaml:"accessKey" json:"accessKey,omitempty" property:"accessKey"` SecretKey string `yaml:"secretKey" json:"secretKey,omitempty" property:"secretKey"` @@ -31,8 +32,7 @@ type AccessKeyPair struct { Options string `yaml:"options" json:"options,omitempty" property:"options"` } -// AccessKeyStorage -// This SPI Extension support us to store our AccessKeyPair or load AccessKeyPair from other +// AccessKeyStorage supports us to store our AccessKeyPair or load AccessKeyPair from other // storage, such as filesystem. type AccessKeyStorage interface { GetAccessKeyPair(protocol.Invocation, *common.URL) *AccessKeyPair diff --git a/filter/authenticator.go b/filter/authenticator.go index ac2c8601d4a0d2e5ae3aed56415d9d23856cb502..71f659d4918293e2eb05b8b7a72b6db1cece42ba 100644 --- a/filter/authenticator.go +++ b/filter/authenticator.go @@ -22,14 +22,13 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// Authenticator +// Authenticator defines how an Authenticator works. +// Custom Authenticator must be set by calling auth.SetAuthenticator before use. type Authenticator interface { - // Sign - // give a sign to request + // Sign adds signature to the invocation Sign(protocol.Invocation, *common.URL) error - // Authenticate - // verify the signature of the request is valid or not + // Authenticate verifies the signature of the request Authenticate(protocol.Invocation, *common.URL) error } diff --git a/filter/filter.go b/filter/filter.go index c069510498c7ac68b2bb2169dfe7132a4ef63229..d20ca72c345c6812f4bce6df5dbaf683429a9874 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -24,9 +24,11 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// Filter +// Filter interface defines the functions of a filter // Extension - Filter type Filter interface { + // Invoke is the core function of a filter, it determins the process of the filter Invoke(context.Context, protocol.Invoker, protocol.Invocation) protocol.Result + // OnResponse updates the results from Invoke and then returns the modified results. OnResponse(context.Context, protocol.Result, protocol.Invoker, protocol.Invocation) protocol.Result } diff --git a/filter/filter_impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go index fbfe7565170c7df468f755a4bd1aadde166a79c1..49cdc2287c28ae0cbbd0fcab3700536595bb0f5e 100644 --- a/filter/filter_impl/access_log_filter.go +++ b/filter/filter_impl/access_log_filter.go @@ -71,14 +71,18 @@ func init() { * * the value of "accesslog" can be "true" or "default" too. * If the value is one of them, the access log will be record in log file which defined in log.yml + * AccessLogFilter is designed to be singleton */ type AccessLogFilter struct { logChan chan AccessLogData } -// Invoke ... +// Invoke will check whether user wants to use this filter. +// If we find the value of key constant.ACCESS_LOG_KEY, we will log the invocation info func (ef *AccessLogFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { accessLog := invoker.GetUrl().GetParam(constant.ACCESS_LOG_KEY, "") + + // the user do not if len(accessLog) > 0 { accessLogData := AccessLogData{data: ef.buildAccessLogData(invoker, invocation), accessLog: accessLog} ef.logIntoChannel(accessLogData) @@ -86,7 +90,7 @@ func (ef *AccessLogFilter) Invoke(ctx context.Context, invoker protocol.Invoker, return invoker.Invoke(ctx, invocation) } -// it won't block the invocation +// logIntoChannel won't block the invocation func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) { select { case ef.logChan <- accessLogData: @@ -97,6 +101,7 @@ func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) { } } +// buildAccessLogData builds the access log data func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation protocol.Invocation) map[string]string { dataMap := make(map[string]string, 16) attachments := invocation.Attachments() @@ -130,11 +135,12 @@ func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation pro return dataMap } -// OnResponse ... +// OnResponse do nothing func (ef *AccessLogFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { return result } +// writeLogToFile actually write the logs into file func (ef *AccessLogFilter) writeLogToFile(data AccessLogData) { accessLog := data.accessLog if isDefault(accessLog) { @@ -156,6 +162,12 @@ func (ef *AccessLogFilter) writeLogToFile(data AccessLogData) { } } +// openLogFile will open the log file with append mode. +// If the file is not found, it will create the file. +// Actually, the accessLog is the filename +// You may find out that, once we want to write access log into log file, +// we open the file again and again. +// It needs to be optimized. func (ef *AccessLogFilter) openLogFile(accessLog string) (*os.File, error) { logFile, err := os.OpenFile(accessLog, os.O_CREATE|os.O_APPEND|os.O_RDWR, LogFileMode) if err != nil { @@ -169,6 +181,12 @@ func (ef *AccessLogFilter) openLogFile(accessLog string) (*os.File, error) { return nil, err } last := fileInfo.ModTime().Format(FileDateFormat) + + // this is confused. + // for example, if the last = '2020-03-04' + // and today is '2020-03-05' + // we will create one new file to log access data + // By this way, we can split the access log based on days. if now != last { err = os.Rename(fileInfo.Name(), fileInfo.Name()+"."+now) if err != nil { @@ -180,11 +198,12 @@ func (ef *AccessLogFilter) openLogFile(accessLog string) (*os.File, error) { return logFile, err } +// isDefault check whether accessLog == true or accessLog == default func isDefault(accessLog string) bool { return strings.EqualFold("true", accessLog) || strings.EqualFold("default", accessLog) } -// GetAccessLogFilter ... +// GetAccessLogFilter return the instance of AccessLogFilter func GetAccessLogFilter() filter.Filter { accessLogFilter := &AccessLogFilter{logChan: make(chan AccessLogData, LogMaxBuffer)} go func() { @@ -195,12 +214,13 @@ func GetAccessLogFilter() filter.Filter { return accessLogFilter } -// AccessLogData ... +// AccessLogData defines the data that will be log into file type AccessLogData struct { accessLog string data map[string]string } +// toLogMessage convert the AccessLogData to String func (ef *AccessLogData) toLogMessage() string { builder := strings.Builder{} builder.WriteString("[") diff --git a/filter/filter_impl/active_filter.go b/filter/filter_impl/active_filter.go index 23f2c8e25609dff89392107251715fe6f5175f09..795de968b57207830cc15fc8a0476bfdc3d2cb43 100644 --- a/filter/filter_impl/active_filter.go +++ b/filter/filter_impl/active_filter.go @@ -39,11 +39,11 @@ func init() { extension.SetFilter(active, GetActiveFilter) } -// ActiveFilter ... +// ActiveFilter tracks the requests status type ActiveFilter struct { } -// Invoke ... +// Invoke starts to record the requests status func (ef *ActiveFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking active filter. %v,%v", invocation.MethodName(), len(invocation.Arguments())) invocation.(*invocation2.RPCInvocation).SetAttachments(dubboInvokeStartTime, strconv.FormatInt(protocol.CurrentTimeMillis(), 10)) @@ -51,7 +51,7 @@ func (ef *ActiveFilter) Invoke(ctx context.Context, invoker protocol.Invoker, in return invoker.Invoke(ctx, invocation) } -// OnResponse ... +// OnResponse update the active count base on the request result. func (ef *ActiveFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { startTime, err := strconv.ParseInt(invocation.(*invocation2.RPCInvocation).AttachmentsByKey(dubboInvokeStartTime, "0"), 10, 64) if err != nil { @@ -64,7 +64,7 @@ func (ef *ActiveFilter) OnResponse(ctx context.Context, result protocol.Result, return result } -// GetActiveFilter ... +// GetActiveFilter creates ActiveFilter instance func GetActiveFilter() filter.Filter { return &ActiveFilter{} } diff --git a/filter/filter_impl/active_filter_test.go b/filter/filter_impl/active_filter_test.go index 8917e9141cad4f22ea201a9a07c2873b584c1f92..d5a51d50d93bd9769001964fbb0ae7905eb24980 100644 --- a/filter/filter_impl/active_filter_test.go +++ b/filter/filter_impl/active_filter_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package filter_impl import ( diff --git a/filter/filter_impl/auth/accesskey_storage.go b/filter/filter_impl/auth/accesskey_storage.go index 5adb9d9ee37329228d1d02dc8802deeede68d327..90d3efb5ad897b874c89745740637804808b5133 100644 --- a/filter/filter_impl/auth/accesskey_storage.go +++ b/filter/filter_impl/auth/accesskey_storage.go @@ -25,13 +25,11 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// DefaultAccesskeyStorage -// The default implementation of AccesskeyStorage +// DefaultAccesskeyStorage is the default implementation of AccesskeyStorage type DefaultAccesskeyStorage struct { } -// GetAccessKeyPair -// get AccessKeyPair from url by the key "accessKeyId" and "secretAccessKey" +// GetAccessKeyPair retrieves AccessKeyPair from url by the key "accessKeyId" and "secretAccessKey" func (storage *DefaultAccesskeyStorage) GetAccessKeyPair(invocation protocol.Invocation, url *common.URL) *filter.AccessKeyPair { return &filter.AccessKeyPair{ AccessKey: url.GetParam(constant.ACCESS_KEY_ID_KEY, ""), @@ -43,6 +41,7 @@ func init() { extension.SetAccesskeyStorages(constant.DEFAULT_ACCESS_KEY_STORAGE, GetDefaultAccesskeyStorage) } +// GetDefaultAccesskeyStorage initiates an empty DefaultAccesskeyStorage func GetDefaultAccesskeyStorage() filter.AccessKeyStorage { return &DefaultAccesskeyStorage{} } diff --git a/filter/filter_impl/auth/consumer_sign.go b/filter/filter_impl/auth/consumer_sign.go index 062744771acf8ccd505265875a103d24afeb06af..945cf3e6e7e728042b5422174162dd5aded50361 100644 --- a/filter/filter_impl/auth/consumer_sign.go +++ b/filter/filter_impl/auth/consumer_sign.go @@ -29,8 +29,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// ConsumerSignFilter -// This filter is working for signing the request on consumer side +// ConsumerSignFilter signs the request on consumer side type ConsumerSignFilter struct { } @@ -38,6 +37,7 @@ func init() { extension.SetFilter(constant.CONSUMER_SIGN_FILTER, getConsumerSignFilter) } +// Invoke retrieves the configured Authenticator to add signature to invocation func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking ConsumerSign filter.") url := invoker.GetUrl() @@ -52,6 +52,7 @@ func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invo return invoker.Invoke(ctx, invocation) } +// OnResponse dummy process, returns the result directly func (csf *ConsumerSignFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } diff --git a/filter/filter_impl/auth/default_authenticator.go b/filter/filter_impl/auth/default_authenticator.go index 2b8d55927807407f350ecc6cfc28b6913a6d1a81..5b86fc148e42b6364bcb0752c31bfbbc3cfa2b9d 100644 --- a/filter/filter_impl/auth/default_authenticator.go +++ b/filter/filter_impl/auth/default_authenticator.go @@ -37,13 +37,11 @@ func init() { extension.SetAuthenticator(constant.DEFAULT_AUTHENTICATOR, GetDefaultAuthenticator) } -// DefaultAuthenticator -// The default implemetation of Authenticator +// DefaultAuthenticator is the default implementation of Authenticator type DefaultAuthenticator struct { } -// Sign -// add the signature for the invocation +// Sign adds the signature to the invocation func (authenticator *DefaultAuthenticator) Sign(invocation protocol.Invocation, url *common.URL) error { currentTimeMillis := strconv.Itoa(int(time.Now().Unix() * 1000)) @@ -84,8 +82,7 @@ func getSignature(url *common.URL, invocation protocol.Invocation, secrectKey st return signature, nil } -// Authenticate -// This method verifies whether the signature sent by the requester is correct +// Authenticate verifies whether the signature sent by the requester is correct func (authenticator *DefaultAuthenticator) Authenticate(invocation protocol.Invocation, url *common.URL) error { accessKeyId := invocation.AttachmentsByKey(constant.AK_KEY, "") @@ -122,6 +119,7 @@ func getAccessKeyPair(invocation protocol.Invocation, url *common.URL) (*filter. } } +// GetDefaultAuthenticator creates an empty DefaultAuthenticator instance func GetDefaultAuthenticator() filter.Authenticator { return &DefaultAuthenticator{} } diff --git a/filter/filter_impl/auth/provider_auth.go b/filter/filter_impl/auth/provider_auth.go index 0d5772e5508894111a88443bfe2d1b02ebfac54a..d5f5db300d4e7c94978d5d52e32f741f7d27bb48 100644 --- a/filter/filter_impl/auth/provider_auth.go +++ b/filter/filter_impl/auth/provider_auth.go @@ -29,8 +29,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// ProviderAuthFilter -// This filter is used to verify the correctness of the signature on provider side +// ProviderAuthFilter verifies the correctness of the signature on provider side type ProviderAuthFilter struct { } @@ -38,6 +37,7 @@ func init() { extension.SetFilter(constant.PROVIDER_AUTH_FILTER, getProviderAuthFilter) } +// Invoke retrieves the configured Authenticator to verify the signature in an invocation func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking providerAuth filter.") url := invoker.GetUrl() @@ -55,6 +55,7 @@ func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invo return invoker.Invoke(ctx, invocation) } +// OnResponse dummy process, returns the result directly func (paf *ProviderAuthFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } diff --git a/filter/filter_impl/auth/sign_util.go b/filter/filter_impl/auth/sign_util.go index 043a549a849dde66712e1bef389dd91a024660df..45170bb8117284275a87a3a57d14ce68d6cc4e9c 100644 --- a/filter/filter_impl/auth/sign_util.go +++ b/filter/filter_impl/auth/sign_util.go @@ -26,12 +26,12 @@ import ( "strings" ) -// Sign -// get a signature string with given information, such as metadata or parameters +// Sign gets a signature string with given bytes func Sign(metadata, key string) string { return doSign([]byte(metadata), key) } +// SignWithParams returns a signature with giving params and metadata. func SignWithParams(params []interface{}, metadata, key string) (string, error) { if params == nil || len(params) == 0 { return Sign(metadata, key), nil @@ -61,6 +61,7 @@ func doSign(bytes []byte, key string) string { return base64.URLEncoding.EncodeToString(signature) } +// IsEmpty verify whether the inputted string is empty func IsEmpty(s string, allowSpace bool) bool { if len(s) == 0 { return true diff --git a/filter/filter_impl/echo_filter.go b/filter/filter_impl/echo_filter.go index a12800a21a8ebe4545b4a8b5bd0f8a30c1462105..7da5ec7029ea698b1bf1a14ad36123fbec3aacf7 100644 --- a/filter/filter_impl/echo_filter.go +++ b/filter/filter_impl/echo_filter.go @@ -38,13 +38,13 @@ func init() { extension.SetFilter(ECHO, GetFilter) } -// EchoFilter +// EchoFilter health check // RPCService need a Echo method in consumer, if you want to use EchoFilter // eg: // Echo func(ctx context.Context, arg interface{}, rsp *Xxx) error type EchoFilter struct{} -// Invoke ... +// Invoke response to the callers with its first argument. func (ef *EchoFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { logger.Infof("invoking echo filter.") logger.Debugf("%v,%v", invocation.MethodName(), len(invocation.Arguments())) @@ -58,7 +58,7 @@ func (ef *EchoFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invo return invoker.Invoke(ctx, invocation) } -// OnResponse ... +// OnResponse dummy process, returns the result directly func (ef *EchoFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { diff --git a/filter/filter_impl/execute_limit_filter.go b/filter/filter_impl/execute_limit_filter.go index 434c378045456eb13317e0a48630ebd33f244c05..bfc5096ca089867f6e6234089e387d3f9b48a3aa 100644 --- a/filter/filter_impl/execute_limit_filter.go +++ b/filter/filter_impl/execute_limit_filter.go @@ -45,9 +45,8 @@ func init() { extension.SetFilter(name, GetExecuteLimitFilter) } +// ExecuteLimitFilter will limit the number of in-progress request and it's thread-safe. /** - * ExecuteLimitFilter - * The filter will limit the number of in-progress request and it's thread-safe. * example: * "UserProvider": * registry: "hangzhouzk" @@ -80,7 +79,7 @@ type ExecuteState struct { concurrentCount int64 } -// Invoke ... +// Invoke judges whether the current processing requests over the threshold func (ef *ExecuteLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { methodConfigPrefix := "methods." + invocation.MethodName() + "." ivkURL := invoker.GetUrl() @@ -122,7 +121,7 @@ func (ef *ExecuteLimitFilter) Invoke(ctx context.Context, invoker protocol.Invok return invoker.Invoke(ctx, invocation) } -// OnResponse ... +// OnResponse dummy process, returns the result directly func (ef *ExecuteLimitFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { return result } @@ -138,7 +137,7 @@ func (state *ExecuteState) decrease() { var executeLimitOnce sync.Once var executeLimitFilter *ExecuteLimitFilter -// GetExecuteLimitFilter ... +// GetExecuteLimitFilter returns the singleton ExecuteLimitFilter instance func GetExecuteLimitFilter() filter.Filter { executeLimitOnce.Do(func() { executeLimitFilter = &ExecuteLimitFilter{ diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go index 9bc131ef8903942b84df2b8fc14fd11143d1a7b6..3f4d714e6b0cbdf48f5e1afce3222a18857041f9 100644 --- a/filter/filter_impl/generic_filter.go +++ b/filter/filter_impl/generic_filter.go @@ -50,7 +50,7 @@ func init() { // GenericFilter ... type GenericFilter struct{} -// Invoke ... +// Invoke turns the parameters to map for generic method func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 { oldArguments := invocation.Arguments() @@ -73,13 +73,13 @@ func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, i return invoker.Invoke(ctx, invocation) } -// OnResponse ... +// OnResponse dummy process, returns the result directly func (ef *GenericFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { return result } -// GetGenericFilter ... +// GetGenericFilter returns GenericFilter instance func GetGenericFilter() filter.Filter { return &GenericFilter{} } diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go index 37c6af7450a75449fce51182684be2f619eda9d8..2a911659f068b53836b87af5caf5773d8ac5f119 100644 --- a/filter/filter_impl/generic_service_filter_test.go +++ b/filter/filter_impl/generic_service_filter_test.go @@ -96,7 +96,7 @@ func TestGenericServiceFilter_Invoke(t *testing.T) { hessian.Object("222")}, } s := &TestService{} - _, _ = common.ServiceMap.Register("testprotocol", s) + _, _ = common.ServiceMap.Register("TestService", "testprotocol", s) rpcInvocation := invocation.NewRPCInvocation(methodName, aurguments, nil) filter := GetGenericServiceFilter() url, _ := common.NewURL("testprotocol://127.0.0.1:20000/com.test.Path") diff --git a/filter/filter_impl/graceful_shutdown_filter.go b/filter/filter_impl/graceful_shutdown_filter.go index 95e625b2d56895a4d57823e4e0e2e7d1d5e90a08..4a4e8ce466edabe82815b99244404ac024d73b26 100644 --- a/filter/filter_impl/graceful_shutdown_filter.go +++ b/filter/filter_impl/graceful_shutdown_filter.go @@ -53,6 +53,7 @@ type gracefulShutdownFilter struct { shutdownConfig *config.ShutdownConfig } +// Invoke adds the requests count and block the new requests if application is closing func (gf *gracefulShutdownFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { if gf.rejectNewRequest() { logger.Info("The application is closing, new request will be rejected.") @@ -62,6 +63,7 @@ func (gf *gracefulShutdownFilter) Invoke(ctx context.Context, invoker protocol.I return invoker.Invoke(ctx, invocation) } +// OnResponse reduces the number of active processes then return the process result func (gf *gracefulShutdownFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { atomic.AddInt32(&gf.activeCount, -1) // although this isn't thread safe, it won't be a problem if the gf.rejectNewRequest() is true. diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index 9fd97b57b677c9aa8ec492151df9aace6dc78b62..711ef71c44192c5a1d76783a3b3d4cbd0b97632c 100644 --- a/filter/filter_impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -55,14 +55,14 @@ var ( //The filter in the server end of dubbo-go can't get the invoke result for now, //this filter ONLY works in CLIENT end (consumer side) temporarily -//Only after the callService logic is integrated into the filter chain of server end can this filter be used, +//Only after the callService logic is integrated into the filter chain of server end then the filter can be used, //which will be done soon func init() { extension.SetFilter(HYSTRIX_CONSUMER, GetHystrixFilterConsumer) extension.SetFilter(HYSTRIX_PROVIDER, GetHystrixFilterProvider) } -// HystrixFilterError ... +// HystrixFilterError implements error interface type HystrixFilterError struct { err error failByHystrix bool @@ -72,12 +72,12 @@ func (hfError *HystrixFilterError) Error() string { return hfError.err.Error() } -// FailByHystrix ... +// FailByHystrix returns whether the fails causing by Hystrix func (hfError *HystrixFilterError) FailByHystrix() bool { return hfError.failByHystrix } -// NewHystrixFilterError ... +// NewHystrixFilterError return a HystrixFilterError instance func NewHystrixFilterError(err error, failByHystrix bool) error { return &HystrixFilterError{ err: err, @@ -92,7 +92,7 @@ type HystrixFilter struct { ifNewMap sync.Map } -// Invoke ... +// Invoke is an implentation of filter, provides Hystrix pattern latency and fault tolerance func (hf *HystrixFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { cmdName := fmt.Sprintf("%s&method=%s", invoker.GetUrl().Key(), invocation.MethodName()) @@ -124,7 +124,7 @@ func (hf *HystrixFilter) Invoke(ctx context.Context, invoker protocol.Invoker, i _, _, err := hystrix.GetCircuit(cmdName) configLoadMutex.RUnlock() if err != nil { - logger.Errorf("[Hystrix Filter]Errors occurred getting circuit for %s , will invoke without hystrix, error is: ", cmdName, err) + logger.Errorf("[Hystrix Filter]Errors occurred getting circuit for %s , will invoke without hystrix, error is: %+v", cmdName, err) return invoker.Invoke(ctx, invocation) } logger.Infof("[Hystrix Filter]Using hystrix filter: %s", cmdName) @@ -154,12 +154,12 @@ func (hf *HystrixFilter) Invoke(ctx context.Context, invoker protocol.Invoker, i return result } -// OnResponse ... +// OnResponse dummy process, returns the result directly func (hf *HystrixFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } -// GetHystrixFilterConsumer ... +// GetHystrixFilterConsumer returns HystrixFilter instance for consumer func GetHystrixFilterConsumer() filter.Filter { //When first called, load the config in consumerConfigOnce.Do(func() { @@ -170,7 +170,7 @@ func GetHystrixFilterConsumer() filter.Filter { return &HystrixFilter{COrP: true} } -// GetHystrixFilterProvider ... +// GetHystrixFilterProvider returns HystrixFilter instance for provider func GetHystrixFilterProvider() filter.Filter { providerConfigOnce.Do(func() { if err := initHystrixConfigProvider(); err != nil { diff --git a/filter/filter_impl/token_filter.go b/filter/filter_impl/token_filter.go index 4605416c40a616361868c313881ae784257e6742..23742c66e94d9ecfc09d004441a54aad86ef049e 100644 --- a/filter/filter_impl/token_filter.go +++ b/filter/filter_impl/token_filter.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package filter_impl @@ -42,10 +42,10 @@ func init() { extension.SetFilter(TOKEN, GetTokenFilter) } -// TokenFilter ... +// TokenFilter will verify if the token is valid type TokenFilter struct{} -// Invoke ... +// Invoke verifies the incoming token with the service configured token func (tf *TokenFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { invokerTkn := invoker.GetUrl().GetParam(constant.TOKEN_KEY, "") if len(invokerTkn) > 0 { @@ -61,7 +61,7 @@ func (tf *TokenFilter) Invoke(ctx context.Context, invoker protocol.Invoker, inv return invoker.Invoke(ctx, invocation) } -// OnResponse ... +// OnResponse dummy process, returns the result directly func (tf *TokenFilter) OnResponse(ctx context.Context, result protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { return result } diff --git a/filter/filter_impl/token_filter_test.go b/filter/filter_impl/token_filter_test.go index 672082c729bc371a40573a66d13bc57a7024186b..b8b297e67267640a1c294541afdd4e062bfebb25 100644 --- a/filter/filter_impl/token_filter_test.go +++ b/filter/filter_impl/token_filter_test.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package filter_impl @@ -53,10 +53,10 @@ func TestTokenFilter_Invoke(t *testing.T) { func TestTokenFilter_InvokeEmptyToken(t *testing.T) { filter := GetTokenFilter() - url := common.URL{} + testUrl := common.URL{} attch := make(map[string]string, 0) attch[constant.TOKEN_KEY] = "ori_key" - result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } @@ -64,23 +64,23 @@ func TestTokenFilter_InvokeEmptyToken(t *testing.T) { func TestTokenFilter_InvokeEmptyAttach(t *testing.T) { filter := GetTokenFilter() - url := common.NewURLWithOptions( + testUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) attch := make(map[string]string, 0) - result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(*testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.NotNil(t, result.Error()) } func TestTokenFilter_InvokeNotEqual(t *testing.T) { filter := GetTokenFilter() - url := common.NewURLWithOptions( + testUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) attch := make(map[string]string, 0) attch[constant.TOKEN_KEY] = "err_key" result := filter.Invoke(context.Background(), - protocol.NewBaseInvoker(*url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + protocol.NewBaseInvoker(*testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.NotNil(t, result.Error()) } diff --git a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go index a9c2ac15a417ffa6ff8f5b8d78d5c6a94877db30..7419a4576122d4db334969b0711666b5b2816e60 100644 --- a/filter/filter_impl/tps/tps_limit_fix_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_fix_window_strategy.go @@ -39,8 +39,8 @@ func init() { extension.SetTpsLimitStrategy(constant.DEFAULT_KEY, creator) } +// FixedWindowTpsLimitStrategyImpl implements the TPS limit strategy base on requests count during the interval /** - * FixedWindowTpsLimitStrategyImpl * It's the same as default implementation in Java * It's not a thread-safe implementation. * It you want to use the thread-safe implementation, please use ThreadSafeFixedWindowTpsLimitStrategyImpl @@ -65,7 +65,8 @@ type FixedWindowTpsLimitStrategyImpl struct { timestamp int64 } -// IsAllowable ... +// IsAllowable determines if the requests over the TPS limit within the interval. +// It is not thread-safe. func (impl *FixedWindowTpsLimitStrategyImpl) IsAllowable() bool { current := time.Now().UnixNano() @@ -82,6 +83,7 @@ func (impl *FixedWindowTpsLimitStrategyImpl) IsAllowable() bool { type fixedWindowStrategyCreator struct{} +// Create returns a FixedWindowTpsLimitStrategyImpl instance with pre-configured limit rate and interval func (creator *fixedWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy { return &FixedWindowTpsLimitStrategyImpl{ rate: int32(rate), diff --git a/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go b/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go index a781cc7bfbf297d0b9cf84ca0aa9dcfbbef7e14b..cbbba19fff65be222cb895dcbe9b2e4d02082985 100644 --- a/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_sliding_window_strategy.go @@ -32,8 +32,8 @@ func init() { extension.SetTpsLimitStrategy("slidingWindow", &slidingWindowStrategyCreator{}) } +// SlidingWindowTpsLimitStrategyImpl implements a thread-safe TPS limit strategy base on requests count. /** - * SlidingWindowTpsLimitStrategyImpl * it's thread-safe. * "UserProvider": * registry: "hangzhouzk" @@ -54,7 +54,8 @@ type SlidingWindowTpsLimitStrategyImpl struct { queue *list.List } -// IsAllowable ... +// IsAllowable determins whether the number of requests within the time window overs the threshold +// It is thread-safe. func (impl *SlidingWindowTpsLimitStrategyImpl) IsAllowable() bool { impl.mutex.Lock() defer impl.mutex.Unlock() @@ -84,6 +85,7 @@ func (impl *SlidingWindowTpsLimitStrategyImpl) IsAllowable() bool { type slidingWindowStrategyCreator struct{} +// Create returns SlidingWindowTpsLimitStrategyImpl instance with configured limit rate and interval func (creator *slidingWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy { return &SlidingWindowTpsLimitStrategyImpl{ rate: rate, diff --git a/filter/filter_impl/tps/tps_limit_strategy_mock.go b/filter/filter_impl/tps/tps_limit_strategy_mock.go index 72c658fb9a5d48b6080900a4645d318dfd2b0c21..c228c7349ce6ad305051e0ba9b26dddee8c12c71 100644 --- a/filter/filter_impl/tps/tps_limit_strategy_mock.go +++ b/filter/filter_impl/tps/tps_limit_strategy_mock.go @@ -1,18 +1,19 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // Code generated by MockGen. DO NOT EDIT. // Source: tps_limit_strategy.go diff --git a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go index 16624836e6397df5adda3f2aa5a80966721a97fb..f78cd8211cd076dcab84759e2bf784d080c72a1c 100644 --- a/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go +++ b/filter/filter_impl/tps/tps_limit_thread_safe_fix_window_strategy.go @@ -32,10 +32,9 @@ func init() { }) } +// ThreadSafeFixedWindowTpsLimitStrategyImpl is the thread-safe implementation. +// It's also a thread-safe decorator of FixedWindowTpsLimitStrategyImpl /** - * ThreadSafeFixedWindowTpsLimitStrategyImpl - * it's the thread-safe implementation. - * Also, it's a thread-safe decorator of FixedWindowTpsLimitStrategyImpl * "UserProvider": * registry: "hangzhouzk" * protocol : "dubbo" @@ -53,7 +52,7 @@ type ThreadSafeFixedWindowTpsLimitStrategyImpl struct { fixedWindow *FixedWindowTpsLimitStrategyImpl } -// IsAllowable ... +// IsAllowable implements thread-safe then run the FixedWindowTpsLimitStrategy func (impl *ThreadSafeFixedWindowTpsLimitStrategyImpl) IsAllowable() bool { impl.mutex.Lock() defer impl.mutex.Unlock() @@ -64,6 +63,7 @@ type threadSafeFixedWindowStrategyCreator struct { fixedWindowStrategyCreator *fixedWindowStrategyCreator } +// Create returns ThreadSafeFixedWindowTpsLimitStrategyImpl instance func (creator *threadSafeFixedWindowStrategyCreator) Create(rate int, interval int) filter.TpsLimitStrategy { fixedWindowStrategy := creator.fixedWindowStrategyCreator.Create(rate, interval).(*FixedWindowTpsLimitStrategyImpl) return &ThreadSafeFixedWindowTpsLimitStrategyImpl{ diff --git a/filter/filter_impl/tps/tps_limiter_method_service.go b/filter/filter_impl/tps/tps_limiter_method_service.go index 7fe8de9237b82415a09083c2be59df5e232ecaf0..5761579a38a22500d54193a9564170cc0215cf0f 100644 --- a/filter/filter_impl/tps/tps_limiter_method_service.go +++ b/filter/filter_impl/tps/tps_limiter_method_service.go @@ -44,9 +44,8 @@ func init() { extension.SetTpsLimiter(name, GetMethodServiceTpsLimiter) } +// MethodServiceTpsLimiterImpl allows developer to config both method-level and service-level tps limiter. /** - * MethodServiceTpsLimiterImpl - * This implementation allows developer to config both method-level and service-level tps limiter. * for example: * "UserProvider": * registry: "hangzhouzk" @@ -115,7 +114,12 @@ type MethodServiceTpsLimiterImpl struct { tpsState *concurrent.Map } -// IsAllowable ... +// IsAllowable based on method-level and service-level. +// The method-level has high priority which means that if there is any rate limit configuration for the method, +// the service-level rate limit strategy will be ignored. +// The key point is how to keep thread-safe +// This implementation use concurrent map + loadOrStore to make implementation thread-safe +// You can image that even multiple threads create limiter, but only one could store the limiter into tpsState func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocation protocol.Invocation) bool { methodConfigPrefix := "methods." + invocation.MethodName() + "." @@ -123,23 +127,30 @@ func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocatio methodLimitRateConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_RATE_KEY, "") methodIntervalConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_INTERVAL_KEY, "") + // service-level tps limit limitTarget := url.ServiceKey() // method-level tps limit if len(methodIntervalConfig) > 0 || len(methodLimitRateConfig) > 0 { + // it means that if the method-level rate limit exist, we will use method-level rate limit strategy limitTarget = limitTarget + "#" + invocation.MethodName() } + // looking up the limiter from 'cache' limitState, found := limiter.tpsState.Load(limitTarget) if found { + // the limiter has been cached, we return its result return limitState.(filter.TpsLimitStrategy).IsAllowable() } + // we could not find the limiter, and try to create one. + limitRate := getLimitConfig(methodLimitRateConfig, url, invocation, constant.TPS_LIMIT_RATE_KEY, constant.DEFAULT_TPS_LIMIT_RATE) if limitRate < 0 { + // the limitTarget is not necessary to be limited. return true } @@ -150,13 +161,20 @@ func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocatio panic(fmt.Sprintf("The interval must be positive, please check your configuration! url: %s", url.String())) } + // find the strategy config and then create one limitStrategyConfig := url.GetParam(methodConfigPrefix+constant.TPS_LIMIT_STRATEGY_KEY, url.GetParam(constant.TPS_LIMIT_STRATEGY_KEY, constant.DEFAULT_KEY)) limitStateCreator := extension.GetTpsLimitStrategyCreator(limitStrategyConfig) + + // we using loadOrStore to ensure thread-safe limitState, _ = limiter.tpsState.LoadOrStore(limitTarget, limitStateCreator.Create(int(limitRate), int(limitInterval))) + return limitState.(filter.TpsLimitStrategy).IsAllowable() } +// getLimitConfig will try to fetch the configuration from url. +// If we can convert the methodLevelConfig to int64, return; +// Or, we will try to look up server-level configuration and then convert it to int64 func getLimitConfig(methodLevelConfig string, url common.URL, invocation protocol.Invocation, @@ -172,6 +190,8 @@ func getLimitConfig(methodLevelConfig string, return result } + // actually there is no method-level configuration, so we use the service-level configuration + result, err := strconv.ParseInt(url.GetParam(configKey, defaultVal), 0, 0) if err != nil { @@ -183,7 +203,7 @@ func getLimitConfig(methodLevelConfig string, var methodServiceTpsLimiterInstance *MethodServiceTpsLimiterImpl var methodServiceTpsLimiterOnce sync.Once -// GetMethodServiceTpsLimiter ... +// GetMethodServiceTpsLimiter will return an MethodServiceTpsLimiterImpl instance. func GetMethodServiceTpsLimiter() filter.TpsLimiter { methodServiceTpsLimiterOnce.Do(func() { methodServiceTpsLimiterInstance = &MethodServiceTpsLimiterImpl{ diff --git a/filter/filter_impl/tps/tps_limiter_mock.go b/filter/filter_impl/tps/tps_limiter_mock.go index 463b0988acbeb17a967c9803337a61c4914bce42..b49084f28e7d4b62d64762170b6dfbbec4a4de96 100644 --- a/filter/filter_impl/tps/tps_limiter_mock.go +++ b/filter/filter_impl/tps/tps_limiter_mock.go @@ -1,18 +1,19 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // Code generated by MockGen. DO NOT EDIT. // Source: tps_limiter.go diff --git a/filter/filter_impl/tps_limit_filter.go b/filter/filter_impl/tps_limit_filter.go index fa78288f9678d67d0eb0d025a83b75493f7fda80..ea1e3bc15e1952799227d712db114ff790527720 100644 --- a/filter/filter_impl/tps_limit_filter.go +++ b/filter/filter_impl/tps_limit_filter.go @@ -39,8 +39,8 @@ func init() { extension.SetFilter(TpsLimitFilterKey, GetTpsLimitFilter) } +// TpsLimitFilter filters the requests by TPS /** - * TpsLimitFilter * if you wish to use the TpsLimiter, please add the configuration into your service provider configuration: * for example: * "UserProvider": @@ -56,7 +56,7 @@ func init() { type TpsLimitFilter struct { } -// Invoke ... +// Invoke gets the configured limter to impose TPS limiting func (t TpsLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { url := invoker.GetUrl() tpsLimiter := url.GetParam(constant.TPS_LIMITER_KEY, "") @@ -72,13 +72,13 @@ func (t TpsLimitFilter) Invoke(ctx context.Context, invoker protocol.Invoker, in return invoker.Invoke(ctx, invocation) } -// OnResponse ... +// OnResponse dummy process, returns the result directly func (t TpsLimitFilter) OnResponse(_ context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { return result } -// GetTpsLimitFilter ... +// GetTpsLimitFilter returns an TpsLimitFilter instance. func GetTpsLimitFilter() filter.Filter { return &TpsLimitFilter{} } diff --git a/filter/handler/rejected_execution_handler_mock.go b/filter/handler/rejected_execution_handler_mock.go index a5bef63b3729a7b04d911c9844320aa778ac357a..bff54769cb007ed2fce5a7623c96edc370e63903 100644 --- a/filter/handler/rejected_execution_handler_mock.go +++ b/filter/handler/rejected_execution_handler_mock.go @@ -1,18 +1,19 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // Code generated by MockGen. DO NOT EDIT. // Source: rejected_execution_handler.go diff --git a/filter/handler/rejected_execution_handler_only_log.go b/filter/handler/rejected_execution_handler_only_log.go index 0f9003c7df2165a2f3a364a5afc47f578db1d243..52ac1765f78172c0062de8884198e759b8d494ca 100644 --- a/filter/handler/rejected_execution_handler_only_log.go +++ b/filter/handler/rejected_execution_handler_only_log.go @@ -36,6 +36,7 @@ const ( ) func init() { + // this implementation is the the default implementation of RejectedExecutionHandler extension.SetRejectedExecutionHandler(HandlerName, GetOnlyLogRejectedExecutionHandler) extension.SetRejectedExecutionHandler(constant.DEFAULT_KEY, GetOnlyLogRejectedExecutionHandler) } @@ -43,8 +44,8 @@ func init() { var onlyLogHandlerInstance *OnlyLogRejectedExecutionHandler var onlyLogHandlerOnce sync.Once +// OnlyLogRejectedExecutionHandler implements the RejectedExecutionHandler /** - * OnlyLogRejectedExecutionHandler * This implementation only logs the invocation info. * it always return en error inside the result. * "UserProvider": @@ -56,11 +57,12 @@ var onlyLogHandlerOnce sync.Once * tps.limit.rejected.handler: "default" or "log" * methods: * - name: "GetUser" + * OnlyLogRejectedExecutionHandler is designed to be singleton */ type OnlyLogRejectedExecutionHandler struct { } -// RejectedExecution ... +// RejectedExecution will do nothing, it only log the invocation. func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL, _ protocol.Invocation) protocol.Result { @@ -68,7 +70,7 @@ func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL return &protocol.RPCResult{} } -// GetOnlyLogRejectedExecutionHandler ... +// GetOnlyLogRejectedExecutionHandler will return the instance of OnlyLogRejectedExecutionHandler func GetOnlyLogRejectedExecutionHandler() filter.RejectedExecutionHandler { onlyLogHandlerOnce.Do(func() { onlyLogHandlerInstance = &OnlyLogRejectedExecutionHandler{} diff --git a/filter/rejected_execution_handler.go b/filter/rejected_execution_handler.go index caeea1db6631d0968fd58f59f9577ee9272f3ca0..3d1e1c1e641a836411ce0f71f97acf5d5a55f6d1 100644 --- a/filter/rejected_execution_handler.go +++ b/filter/rejected_execution_handler.go @@ -22,8 +22,8 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// RejectedExecutionHandler defines the handler to handle exceptions from invoking filters. /** - * RejectedExecutionHandler * If the invocation cannot pass any validation in filter, like ExecuteLimitFilter and TpsLimitFilter, * the implementation will be used. * The common case is that sometimes you want to return the default value when the request was rejected. @@ -31,5 +31,7 @@ import ( * In such situation, implement this interface and register it by invoking extension.SetRejectedExecutionHandler. */ type RejectedExecutionHandler interface { + + // RejectedExecution will be called if the invocation was rejected by some component. RejectedExecution(url common.URL, invocation protocol.Invocation) protocol.Result } diff --git a/filter/tps_limit_strategy.go b/filter/tps_limit_strategy.go index 5edf32ce1912642c7ad0ea0b3f6144b45c267eb4..2ee876a0b340ed1f87b94c35b149b548371e2bf9 100644 --- a/filter/tps_limit_strategy.go +++ b/filter/tps_limit_strategy.go @@ -17,8 +17,8 @@ package filter +// TpsLimitStrategy defines how to do the TPS limiting in method level. /* - * TpsLimitStrategy * please register your implementation by invoking SetTpsLimitStrategy * "UserProvider": * registry: "hangzhouzk" @@ -33,10 +33,16 @@ package filter * tps.limit.strategy: "name of implementation" # method-level */ type TpsLimitStrategy interface { + // IsAllowable will return true if this invocation is not over limitation IsAllowable() bool } -// TpsLimitStrategyCreator ... +// TpsLimitStrategyCreator is the creator abstraction for TpsLimitStrategy type TpsLimitStrategyCreator interface { - Create(rate int, interval int) TpsLimitStrategy + // Create will create an instance of TpsLimitStrategy + // It will be a little hard to understand this method. + // The unit of interval is ms + // for example, if the limit = 100, interval = 1000 + // which means that the tps limitation is 100 times per 1000ms (100/1000ms) + Create(limit int, interval int) TpsLimitStrategy } diff --git a/filter/tps_limiter.go b/filter/tps_limiter.go index dbc9f76838a4406b4788e7757453098613253d58..8385d7b5d84a420b54df6bf51e32a35d17e1b249 100644 --- a/filter/tps_limiter.go +++ b/filter/tps_limiter.go @@ -22,8 +22,8 @@ import ( "github.com/apache/dubbo-go/protocol" ) +// TpsLimiter defines the Limiter that judge if the TPS overs the threshold /* - * TpsLimiter * please register your implementation by invoking SetTpsLimiter * The usage, for example: * "UserProvider": @@ -34,5 +34,6 @@ import ( * tps.limiter: "the name of limiter", */ type TpsLimiter interface { + // IsAllowable will check whether this invocation should be enabled for further process IsAllowable(common.URL, protocol.Invocation) bool } diff --git a/go.mod b/go.mod index be84af853a66a3500e1d306df667cdd646534765..5a7efac66d900d7bcab0256cd927088e2543fd69 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,18 @@ module github.com/apache/dubbo-go require ( github.com/Workiva/go-datastructures v1.0.50 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 - github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e // indirect - github.com/apache/dubbo-go-hessian2 v1.4.0 - github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect + github.com/apache/dubbo-go-hessian2 v1.5.0 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 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/creasty/defaults v1.3.0 - github.com/dubbogo/getty v1.3.3 + github.com/dubbogo/getty v1.3.5 github.com/dubbogo/go-zookeeper v1.0.0 - github.com/dubbogo/gost v1.7.0 + github.com/dubbogo/gost v1.9.0 github.com/emicklei/go-restful/v3 v3.0.0 - github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect - github.com/go-errors/errors v1.0.1 // indirect + github.com/go-co-op/gocron v0.1.1 github.com/go-resty/resty/v2 v2.1.0 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect github.com/golang/mock v1.3.1 @@ -29,32 +26,27 @@ require ( github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/hashicorp/consul v1.5.3 github.com/hashicorp/consul/api v1.1.0 - github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 - github.com/jonboulle/clockwork v0.1.0 // indirect - github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect - github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect - github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect + github.com/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.0.0-20190723125407-0242d42e3dbb + github.com/nacos-group/nacos-sdk-go v0.3.1 github.com/opentracing/opentracing-go v1.1.0 - github.com/pkg/errors v0.8.1 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.1.0 - github.com/satori/go.uuid v1.2.0 + github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/soheilhy/cmux v0.1.4 // indirect github.com/stretchr/testify v1.5.1 - github.com/tebeka/strftime v0.1.3 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect - github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 - go.etcd.io/bbolt v1.3.3 // indirect - go.etcd.io/etcd v3.3.13+incompatible + go.etcd.io/bbolt v1.3.4 // indirect go.uber.org/atomic v1.4.0 go.uber.org/zap v1.10.0 + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/grpc v1.22.1 gopkg.in/yaml.v2 v2.2.2 k8s.io/api v0.0.0-20190325185214-7544f9db76f6 @@ -64,3 +56,5 @@ require ( ) go 1.13 + +replace github.com/nacos-group/nacos-sdk-go => /Users/mindeng/go-workspace/src/nacos-sdk-go diff --git a/go.sum b/go.sum index d4f8a367b0e5e184e9a608b5e5a485f985f5c5dd..65f2b7c7b11a8eef8da5885d6f0f0b742cda9622 100644 --- a/go.sum +++ b/go.sum @@ -35,11 +35,10 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e h1:MSuLXx/mveDbpDNhVrcWTMeV4lbYWKcyO4rH+jAxmX0= -github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190802083043-4cd0c391755e/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= -github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/apache/dubbo-go-hessian2 v1.4.0 h1:Cb9FQVTy3G93dnDr7P93U8DeKFYpDTJjQp44JG5TafA= -github.com/apache/dubbo-go-hessian2 v1.4.0/go.mod h1:VwEnsOMidkM1usya2uPfGpSLO9XUF//WQcWn3y+jFz8= +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/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/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,7 +50,6 @@ 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= @@ -106,15 +104,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.3 h1:8m4zZBqFHO+NmhH7rMPlFuuYRVjcPD7cUhumevqMZZs= -github.com/dubbogo/getty v1.3.3/go.mod h1:U92BDyJ6sW9Jpohr2Vlz8w2uUbIbNZ3d+6rJvFTSPp0= +github.com/dubbogo/getty v1.3.5 h1:xJxdDj9jm7wlrRSsVZSk2TDNxJbbac5GpxV0QpjO+Tw= +github.com/dubbogo/getty v1.3.5/go.mod h1:T55vN8Q6tZjf2AQZiGmkujneD3LfqYbv2b3QjacwYOY= github.com/dubbogo/go-zookeeper v1.0.0 h1:RsYdlGwhDW+iKXM3eIIcvt34P2swLdmQfuIJxsHlGoM= github.com/dubbogo/go-zookeeper v1.0.0/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.5.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8= -github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/dubbogo/gost v1.7.0 h1:lWNBIE2hk1Aj2be2uXkyRTpZG0RQZj0/xbXnkIq6EHE= -github.com/dubbogo/gost v1.7.0/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= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0 h1:ZoRgc53qJCfSLimXqJDrmBhnt5GChDsExMCK7t48o0Y= @@ -138,6 +134,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= @@ -153,6 +151,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= @@ -324,6 +323,12 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9 h1:hJix6idebFclqlfZCHE7EUX7uqLCyb70nHNHH1XKGBg= +github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b h1:Rrp0ByJXEjhREMPGTt3aWYjoIsUGCbt21ekbeJcTWv0= +github.com/juju/testing v0.0.0-20191001232224-ce9dec17d28b/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b h1:VE6r2OwP5gj+Z9aCkSKl3MlmnZbfMAjhvR5T7abKHEo= github.com/keybase/go-crypto v0.0.0-20180614160407-5114a9a81e1b/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= @@ -383,8 +388,8 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb h1:lbmvw8r9W55w+aQgWn35W1nuleRIECMoqUrmwAOAvoI= -github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb/go.mod h1:CEkSvEpoveoYjA81m4HNeYQ0sge0LFGKSEqO3JKHllo= +github.com/nacos-group/nacos-sdk-go v0.3.1 h1:MI7bNDAN5m9UFcRRUTSPfJi4dCQo+TYG85qVB1rCHeg= +github.com/nacos-group/nacos-sdk-go v0.3.1/go.mod h1:ESKb6yF0gxSc8GuS+0jaMBe+n8rJ5/k4ya6LyFG2xi8= 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= @@ -392,10 +397,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= @@ -418,6 +427,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -448,6 +459,8 @@ github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1T github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= +github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v0.0.0-20181107111621-48177ef5f880 h1:1Ge4j/3uB2rxzPWD3TC+daeCw+w91z8UCUL/7WH5gn8= @@ -498,10 +511,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8 h1:k8TV7Gz7cpWpOw/dz71fx8cCZdWoPuckHJ/wkJl+meg= github.com/zouyx/agollo v0.0.0-20191114083447-dde9fc9f35b8/go.mod h1:S1cAa98KMFv4Sa8SbJ6ZtvOmf0VlgH0QJ1gXI0lBfBY= -go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw= -go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= +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/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= @@ -527,6 +538,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/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= @@ -535,6 +548,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= @@ -548,6 +563,8 @@ golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/integrate_test.sh b/integrate_test.sh new file mode 100644 index 0000000000000000000000000000000000000000..c9c2f23b5b07f0baf96260d8092e7464d4d15659 --- /dev/null +++ b/integrate_test.sh @@ -0,0 +1,66 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#!/bin/bash + +set -e +set -x + +echo 'start integrate-test' + +# set root workspace +ROOT_DIR=$(pwd) +echo "integrate-test root work-space -> ${ROOT_DIR}" + +# show all travis-env +echo "travis current commit id -> ${TRAVIS_COMMIT}" +echo "travis pull request -> ${TRAVIS_PULL_REQUEST}" +echo "travis pull request branch -> ${TRAVIS_PULL_REQUEST_BRANCH}" +echo "travis pull request slug -> ${TRAVIS_PULL_REQUEST_SLUG}" +echo "travis pull request sha -> ${TRAVIS_PULL_REQUEST_SHA}" +echo "travis pull request repo slug -> ${TRAVIS_REPO_SLUG}" + + +# #start etcd registry insecure listen in [:]:2379 +# docker run -d --network host k8s.gcr.io/etcd:3.3.10 etcd +# echo "etcdv3 listen in [:]2379" + +# #start consul registry insecure listen in [:]:8500 +# docker run -d --network host consul +# echo "consul listen in [:]8500" + +# #start nacos registry insecure listen in [:]:8848 +# docker run -d --network host nacos/nacos-server:latest +# echo "ncacos listen in [:]8848" + +# default use zk as registry +#start zookeeper registry insecure listen in [:]:2181 +docker run -d --network host zookeeper +echo "zookeeper listen in [:]2181" + +# build go-server image +cd ./test/integrate/dubbo/go-server +docker build . -t ci-provider --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA} +cd ${ROOT_DIR} +docker run -d --network host ci-provider + +# build go-client image +cd ./test/integrate/dubbo/go-client +docker build . -t ci-consumer --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_SLUG} --build-arg PR_ORIGIN_COMMITID=${TRAVIS_PULL_REQUEST_SHA} +cd ${ROOT_DIR} +# run provider +# check consumer status +docker run -it --network host ci-consumer 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 a314671055be523844fd7d8f9589b8b6031632bc..2371f7ca02f403a11251b9b0cbb23369b27683e2 100644 --- a/metadata/identifier/base_metadata_identifier.go +++ b/metadata/identifier/base_metadata_identifier.go @@ -25,19 +25,21 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -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 } -type BaseServiceMetadataIdentifier struct { - serviceInterface string - version string - group string - side string +// BaseMetadataIdentifier is the base implement of BaseMetadataIdentifier interface +type BaseMetadataIdentifier struct { + ServiceInterface string + Version string + Group string + Side string } -// joinParams... +// 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 { @@ -47,29 +49,29 @@ func joinParams(joinChar string, params []string) string { return joinedStr } -// getIdentifierKey... -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 + +// getIdentifierKey returns string that format is service:Version:Group:Side:param1:param2... +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... -func (mdi *BaseServiceMetadataIdentifier) getFilePathKey(params ...string) string { - path := serviceToPath(mdi.serviceInterface) +// getFilePathKey returns string that format is metadata/path/Version/Group/Side/param1/param2... +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... +// serviceToPath uss URL encode to decode the @serviceInterface func serviceToPath(serviceInterface string) string { if serviceInterface == constant.ANY_VALUE { return "" @@ -83,7 +85,7 @@ func serviceToPath(serviceInterface string) string { } -//withPathSeparator... +// 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 f3df8f36546093a826279c4e9ec1546f78d444bd..7e50c4c6b9427bd9d439daa7464d96a2ea94fd39 100644 --- a/metadata/identifier/metadata_identifier.go +++ b/metadata/identifier/metadata_identifier.go @@ -17,17 +17,18 @@ package identifier +// MetadataIdentifier is inherit baseMetaIdentifier with Application name type MetadataIdentifier struct { - application string + Application string BaseMetadataIdentifier } -// getIdentifierKey... -func (mdi *MetadataIdentifier) getIdentifierKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.application) +// GetIdentifierKey returns string that format is service:Version:Group:Side:Application +func (mdi *MetadataIdentifier) GetIdentifierKey() string { + return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Application) } -// getIdentifierKey... -func (mdi *MetadataIdentifier) getFilePathKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.application) +// GetFilePathKey returns string that format is metadata/path/Version/Group/Side/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 373df0130dd1f87e3175918bde50060c4be89616..b9e65967e0f707a6efcc9f8ded2ce5dec4f058b8 100644 --- a/metadata/identifier/service_metadata_identifier.go +++ b/metadata/identifier/service_metadata_identifier.go @@ -18,21 +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 } -// getIdentifierKey... -func (mdi *ServiceMetadataIdentifier) getIdentifierKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision) +// 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... -func (mdi *ServiceMetadataIdentifier) getFilePathKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.protocol + constant.KEY_REVISON_PREFIX + mdi.revision) +// GetIdentifierKey returns string that format is service:Version:Group:Side:Protocol:"revision"+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() string { + return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.Protocol, constant.KEY_REVISON_PREFIX+mdi.Revision) } diff --git a/metadata/service.go b/metadata/identifier/service_metadata_identifier_test.go similarity index 54% rename from metadata/service.go rename to metadata/identifier/service_metadata_identifier_test.go index d85703c95a57183d5c0a5b2445839e946dc6a59b..d7ef44a4bbc7611b6391122f8f5841db349eb036 100644 --- a/metadata/service.go +++ b/metadata/identifier/service_metadata_identifier_test.go @@ -15,23 +15,31 @@ * limitations under the License. */ -package metadata +package identifier import ( - "github.com/apache/dubbo-go/common" - gxset "github.com/dubbogo/gost/container/set" + "testing" ) -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) +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()) +} - GetExportedURLs(serviceInterface string, group string, version string, protocol string) gxset.HashSet - GetServiceDefinition(interfaceName string, version string, group string) string - GetServiceDefinitionByServiceKey(serviceKey string) string +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 fd3a290b41e870674366943e12a396c3dae7e238..b1e37db971ada56a77bc3b716606b6fc8d137d34 100644 --- a/metadata/identifier/subscribe_metadata_identifier.go +++ b/metadata/identifier/subscribe_metadata_identifier.go @@ -1,16 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package identifier +// SubscriberMetadataIdentifier is inherit baseMetaIdentifier with service params: Revision type SubscriberMetadataIdentifier struct { - revision string - BaseMetadataIdentifier + Revision string + MetadataIdentifier } -// getIdentifierKey... -func (mdi *SubscriberMetadataIdentifier) getIdentifierKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.revision) +// GetIdentifierKey returns string that format is service:Version:Group:Side:Revision +func (mdi *SubscriberMetadataIdentifier) GetIdentifierKey() string { + return mdi.BaseMetadataIdentifier.getIdentifierKey(mdi.Revision) } -// getIdentifierKey... -func (mdi *SubscriberMetadataIdentifier) getFilePathKey(params ...string) string { - return mdi.BaseMetadataIdentifier.getFilePathKey(mdi.revision) +// GetFilePathKey returns string that format is metadata/path/Version/Group/Side/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/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/delegate/delegate_report.go b/metadata/report/delegate/delegate_report.go new file mode 100644 index 0000000000000000000000000000000000000000..a91c973ed07cade931295462407fa87ad9877617 --- /dev/null +++ b/metadata/report/delegate/delegate_report.go @@ -0,0 +1,277 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + "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 { + 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 { + report := instance.GetMetadataReportInstance() + if mr.syncReport { + return report.SaveSubscribedData(identifier, urls) + } + go report.SaveSubscribedData(identifier, urls) + return nil +} + +// GetSubscribedURLs will delegate to call remote metadata's sdk to get subscribed urls +func (MetadataReport) GetSubscribedURLs(identifier *identifier.SubscriberMetadataIdentifier) []string { + 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 { + 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..04c9e6483929d3ed58fd85337db6ccb4ebd53d00 --- /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" + retryPeroidKey := "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, retryPeroidKey, 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..0d49ff17838e4427613b343d16f00b34bd90a00d --- /dev/null +++ b/metadata/report/etcd/report.go @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + "strings" + "time" +) + +import ( + 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/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("etcd", 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 { + content, err := e.client.Get(e.getNodeKey(metadataIdentifier)) + if err != nil { + logger.Errorf("etcdMetadataReport GetExportedURLs err:{%v}", err.Error()) + return nil + } + if content == "" { + return []string{} + } + return []string{content} +} + +// SaveSubscribedData will convert the urlList to json array and then store it +func (e *etcdMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urlList []common.URL) error { + if len(urlList) == 0 { + logger.Warnf("The url list is empty") + return nil + } + urlStrList := make([]string, 0, len(urlList)) + + for _, e := range urlList { + urlStrList = append(urlStrList, e.String()) + } + + bytes, err := json.Marshal(urlStrList) + + if err != nil { + return perrors.WithMessage(err, "Could not convert the array to json") + } + key := e.getNodeKey(subscriberMetadataIdentifier) + return e.client.Create(key, string(bytes)) +} + +// GetSubscribedURLs will lookup the url +// if not found, an empty list will be returned +func (e *etcdMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) []string { + content, err := e.client.Get(e.getNodeKey(subscriberMetadataIdentifier)) + if err != nil { + logger.Errorf("etcdMetadataReport GetSubscribedURLs err:{%v}", err.Error()) + } + return []string{content} +} + +// GetServiceDefinition will lookup the service definition +func (e *etcdMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) string { + key := e.getNodeKey(metadataIdentifier) + content, err := e.client.Get(key) + if err != nil { + logger.Errorf("etcdMetadataReport GetServiceDefinition err:{%v}", err.Error()) + return "" + } + return content +} + +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..8219cb83c723b1472f8ed7d999f5c3e3859db65f --- /dev/null +++ b/metadata/report/etcd/report_test.go @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 ( + "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([]common.URL, 0, 1) + urlList = append(urlList, serviceUrl) + err = metadataReport.SaveSubscribedData(subMi, urlList) + 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 76% rename from metadata/report_factory.go rename to metadata/report/factory/report_factory.go index 19b1004eee57073acec13c7f114179c47c73f145..8769ebdd2fd1f088415232bd4463d02f7ebd730f 100644 --- a/metadata/report_factory.go +++ b/metadata/report/factory/report_factory.go @@ -15,16 +15,21 @@ * 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 + MetadataReportInstance report.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..2bf40db519b2f131de586f5f506339286baf5c88 --- /dev/null +++ b/metadata/report/nacos/report.go @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + "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() { + ins := &nacosMetadataReportFactory{} + extension.SetMetadataReportFactory("nacos", func() factory.MetadataReportFactory { + return ins + }) +} + +// nacosMetadataReport is the implementation of MetadataReport based Nacos +type nacosMetadataReport struct { + client config_client.IConfigClient +} + +// StoreProviderMetadata will store the metadata +// metadata including the basic info of the server, provider info, and other user custom info +func (n *nacosMetadataReport) StoreProviderMetadata(providerIdentifier *identifier.MetadataIdentifier, serviceDefinitions string) error { + return n.storeMetadata(vo.ConfigParam{ + DataId: providerIdentifier.GetIdentifierKey(), + Group: providerIdentifier.Group, + Content: serviceDefinitions, + }) +} + +// StoreConsumerMetadata will store the metadata +// metadata including the basic info of the server, consumer info, and other user custom info +func (n *nacosMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *identifier.MetadataIdentifier, serviceParameterString string) error { + return n.storeMetadata(vo.ConfigParam{ + DataId: consumerMetadataIdentifier.GetIdentifierKey(), + Group: consumerMetadataIdentifier.Group, + Content: serviceParameterString, + }) +} + +// SaveServiceMetadata will store the metadata +// metadata including the basic info of the server, service info, and other user custom info +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 will remove the service metadata +func (n *nacosMetadataReport) RemoveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier) error { + return n.deleteMetadata(vo.ConfigParam{ + DataId: metadataIdentifier.GetIdentifierKey(), + Group: metadataIdentifier.Group, + }) +} + +// GetExportedURLs will look up the exported urls. +// if not found, an empty list will be returned. +func (n *nacosMetadataReport) GetExportedURLs(metadataIdentifier *identifier.ServiceMetadataIdentifier) []string { + return n.getConfigAsArray(vo.ConfigParam{ + DataId: metadataIdentifier.GetIdentifierKey(), + Group: metadataIdentifier.Group, + }) +} + +// SaveSubscribedData will convert the urlList to json array and then store it +func (n *nacosMetadataReport) SaveSubscribedData(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier, urlList []common.URL) error { + if len(urlList) == 0 { + logger.Warnf("The url list is empty") + return nil + } + urlStrList := make([]string, 0, len(urlList)) + + for _, e := range urlList { + urlStrList = append(urlStrList, e.String()) + } + + bytes, err := json.Marshal(urlStrList) + + if err != nil { + return perrors.WithMessage(err, "Could not convert the array to json") + } + return n.storeMetadata(vo.ConfigParam{ + DataId: subscriberMetadataIdentifier.GetIdentifierKey(), + Group: subscriberMetadataIdentifier.Group, + Content: string(bytes), + }) +} + +// GetSubscribedURLs will lookup the url +// if not found, an empty list will be returned +func (n *nacosMetadataReport) GetSubscribedURLs(subscriberMetadataIdentifier *identifier.SubscriberMetadataIdentifier) []string { + return n.getConfigAsArray(vo.ConfigParam{ + DataId: subscriberMetadataIdentifier.GetIdentifierKey(), + Group: subscriberMetadataIdentifier.Group, + }) +} + +// GetServiceDefinition will lookup the service definition +func (n *nacosMetadataReport) GetServiceDefinition(metadataIdentifier *identifier.MetadataIdentifier) string { + 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 { + cfg := n.getConfig(param) + res := make([]string, 0, 1) + if len(cfg) == 0 { + return res + } + decodeCfg, err := url.QueryUnescape(cfg) + if err != nil { + logger.Errorf("The config is invalid: %s", cfg) + return res + } + res = append(res, decodeCfg) + return res +} + +// getConfig will read the config +func (n *nacosMetadataReport) getConfig(param vo.ConfigParam) string { + cfg, err := n.client.GetConfig(param) + if err != nil { + logger.Errorf("Finding the configuration failed: %v, err: %v", param, err) + } + return cfg +} + +type nacosMetadataReportFactory struct { +} + +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..153242f8e019c04ea3f5ef8e5ed090c1f23e9d41 --- /dev/null +++ b/metadata/report/nacos/report_test.go @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package nacos + +import ( + "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) + + consumerMi := newMetadataIdentifier("client") + consumerMeta := "consumer" + err = rpt.StoreConsumerMetadata(consumerMi, consumerMeta) + assert.Nil(t, err) + + serviceMi := newServiceMetadataIdentifier() + serviceUrl, _ := common.NewURL("registry://localhost:8848", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + + err = rpt.SaveServiceMetadata(serviceMi, serviceUrl) + assert.Nil(t, err) + + subMi := newSubscribeMetadataIdentifier() + urlList := make([]common.URL, 0, 1) + urlList = append(urlList, serviceUrl) + err = rpt.SaveSubscribedData(subMi, urlList) + 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.go b/metadata/report/report.go similarity index 72% rename from metadata/report.go rename to metadata/report/report.go index 3fcc71241411d4a8f9577bb5fb3233e67942cd52..61cdda1f9663a9f4eaed157d7c0232e4e911c80d 100644 --- a/metadata/report.go +++ b/metadata/report/report.go @@ -15,21 +15,21 @@ * limitations under the License. */ -package metadata +package report import ( "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/metadata/definition" "github.com/apache/dubbo-go/metadata/identifier" ) +// MetadataReport is an interface of remote metadata report type MetadataReport interface { - StoreProviderMetadata(*identifier.MetadataIdentifier, *definition.ServiceDefinition) - StoreConsumeretadata(*identifier.MetadataIdentifier, map[string]string) - SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, *common.URL) - RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) + StoreProviderMetadata(*identifier.MetadataIdentifier, string) error + StoreConsumerMetadata(*identifier.MetadataIdentifier, string) error + SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error + RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error GetExportedURLs(*identifier.ServiceMetadataIdentifier) []string - SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, []*common.URL) + SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, []common.URL) error GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) []string - GetServiceDefinition(*identifier.MetadataIdentifier) + GetServiceDefinition(*identifier.MetadataIdentifier) string } diff --git a/metadata/service/exporter/configurable/exporter.go b/metadata/service/exporter/configurable/exporter.go new file mode 100644 index 0000000000000000000000000000000000000000..f8b4b0c0174cb0e5a8753b814f89ed4d332e2fbe --- /dev/null +++ b/metadata/service/exporter/configurable/exporter.go @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package configurable + +import ( + "context" + "sync" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/service" + "github.com/apache/dubbo-go/metadata/service/exporter" +) + +// MetadataServiceExporter is the ConfigurableMetadataServiceExporter which implement MetadataServiceExporter interface +type MetadataServiceExporter struct { + ServiceConfig *config.ServiceConfig + lock sync.RWMutex + metadataService service.MetadataService +} + +// NewMetadataServiceExporter will return a service_exporter.MetadataServiceExporter with the specified metadata service +func NewMetadataServiceExporter(metadataService service.MetadataService) exporter.MetadataServiceExporter { + return &MetadataServiceExporter{ + metadataService: metadataService, + } +} + +// Export will export the metadataService +func (exporter *MetadataServiceExporter) Export() error { + if !exporter.IsExported() { + + serviceConfig := config.NewServiceConfig(constant.SIMPLE_METADATA_SERVICE_NAME, context.Background()) + serviceConfig.Protocol = constant.DEFAULT_PROTOCOL + serviceConfig.Protocols = map[string]*config.ProtocolConfig{ + constant.DEFAULT_PROTOCOL: generateMetadataProtocol(), + } + serviceConfig.InterfaceName = constant.METADATA_SERVICE_NAME + // identify this is a golang server + serviceConfig.Params = map[string]string{} + serviceConfig.Group = config.GetApplicationConfig().Name + // now the error will always be nil + serviceConfig.Version, _ = exporter.metadataService.Version() + + var err error + func() { + exporter.lock.Lock() + defer exporter.lock.Unlock() + exporter.ServiceConfig = serviceConfig + exporter.ServiceConfig.Implement(exporter.metadataService) + err = exporter.ServiceConfig.Export() + }() + + logger.Infof("The MetadataService exports urls : %v ", exporter.ServiceConfig.GetExportedUrls()) + return err + } + logger.Warnf("The MetadataService has been exported : %v ", exporter.ServiceConfig.GetExportedUrls()) + return nil +} + +// Unexport will unexport the metadataService +func (exporter *MetadataServiceExporter) Unexport() { + if exporter.IsExported() { + exporter.ServiceConfig.Unexport() + } +} + +// GetExportedURLs will return the urls that export use. +// Notice锛乀he exported url is not same as url in registry , for example it lack the ip. +func (exporter *MetadataServiceExporter) GetExportedURLs() []*common.URL { + return exporter.ServiceConfig.GetExportedUrls() +} + +// isExported will return is metadataServiceExporter exported or not +func (exporter *MetadataServiceExporter) IsExported() bool { + exporter.lock.RLock() + defer exporter.lock.RUnlock() + return exporter.ServiceConfig != nil && exporter.ServiceConfig.IsExport() +} + +// generateMetadataProtocol will return a default ProtocolConfig +func generateMetadataProtocol() *config.ProtocolConfig { + return &config.ProtocolConfig{ + Name: constant.DEFAULT_PROTOCOL, + Port: "20000", + } +} diff --git a/metadata/service/exporter/configurable/exporter_test.go b/metadata/service/exporter/configurable/exporter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..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..6fe44cfc71c9f6532035a4df081d7ce4a653bf1f --- /dev/null +++ b/metadata/service/inmemory/service.go @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 { + // judge is consumer or provider + // side := url.GetParam(constant.SIDE_KEY, "") + // var service event.RPCService + service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) + // if side == event.RoleType(event.CONSUMER).Role() { + // //TODO:generate the service definition and store it + // + // } else if side == event.RoleType(event.PROVIDER).Role() { + // //TODO:generate the service definition and store it + // } + sd := definition.BuildServiceDefinition(*service, url) + data, err := sd.ToBytes() + if err != nil { + logger.Errorf("publishProvider getServiceDescriptor error. providerUrl:%v , error:%v ", url, err) + } + 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..840bd9a9d5629921840039597cfb06ae4bef2c6b --- /dev/null +++ b/metadata/service/inmemory/service_proxy.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 inmemory + +import ( + "context" + "reflect" + "time" +) + +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})) + + start := time.Now() + res := m.invkr.Invoke(context.Background(), inv) + end := time.Now() + logger.Infof("duration: %s, result: %v", (end.Sub(start)).String(), res.Result()) + 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..af9d1f0079a3367f1dbdd9f543865d80ceacf8fc --- /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 +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..b7fe6f46854e649bae2df1b0a9916002af0ba8c9 --- /dev/null +++ b/metadata/service/remote/service_proxy.go @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 := m.report.GetExportedURLs(&identifier.ServiceMetadataIdentifier{ + BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ + ServiceInterface: serviceInterface, + Version: version, + Group: group, + Side: constant.PROVIDER_PROTOCOL, + }, + Revision: m.revision, + Protocol: protocol, + }) + 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, + }), nil +} + +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..31a9ba1fd128b6b4e3bd6d20702e279b0a067762 --- /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 { + panic("implement me") +} + +func (m mockMetadataReport) RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error { + panic("implement me") +} + +func (m mockMetadataReport) GetExportedURLs(*identifier.ServiceMetadataIdentifier) []string { + return []string{"mock://localhost1", "mock://localhost2"} +} + +func (m mockMetadataReport) SaveSubscribedData(*identifier.SubscriberMetadataIdentifier, []common.URL) error { + panic("implement me") +} + +func (m mockMetadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) []string { + panic("implement me") +} + +func (m mockMetadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) string { + return "definition" +} diff --git a/metadata/service/remote/service_test.go b/metadata/service/remote/service_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2bf1c4c6c00f0cf4b6b6d0eefc3274e081c3cef2 --- /dev/null +++ b/metadata/service/remote/service_test.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 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) +var subscribedMetadata = make(map[*identifier.SubscriberMetadataIdentifier][]common.URL, 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 { + return nil +} + +func (mr *metadataReport) SaveSubscribedData(id *identifier.SubscriberMetadataIdentifier, urls []common.URL) error { + logger.Infof("SaveSubscribedData, , url is %v", urls) + subscribedMetadata[id] = urls + return nil +} + +func (metadataReport) GetSubscribedURLs(*identifier.SubscriberMetadataIdentifier) []string { + return nil +} + +func (metadataReport) GetServiceDefinition(*identifier.MetadataIdentifier) string { + return "" +} + +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") + assert.Equal(t, 1, len(serviceMetadata)) + assert.Equal(t, 1, len(subscribedMetadata)) +} + +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" + + 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) + + mts.SubscribeURL(u) + + 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) + 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/metrics/prometheus/reporter.go b/metrics/prometheus/reporter.go index 1636b14da2fe5ab714853aa662eaa774ddbc1791..bd1e7986ca709a4e10dfcad04d2380931308d568 100644 --- a/metrics/prometheus/reporter.go +++ b/metrics/prometheus/reporter.go @@ -68,9 +68,8 @@ func init() { extension.SetMetricReporter(reporterName, newPrometheusReporter) } -// PrometheusReporter -// it will collect the data for Prometheus -// if you want to use this, you should initialize your prometheus. +// PrometheusReporter will collect the data for Prometheus +// if you want to use this feature, you need to initialize your prometheus. // https://prometheus.io/docs/guides/go-application/ type PrometheusReporter struct { @@ -85,7 +84,7 @@ type PrometheusReporter struct { consumerHistogramVec *prometheus.HistogramVec } -// Report report the duration to Prometheus +// Report reports the duration to Prometheus // the role in url must be consumer or provider // or it will be ignored func (reporter *PrometheusReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) { @@ -99,7 +98,7 @@ func (reporter *PrometheusReporter) Report(ctx context.Context, invoker protocol sumVec = reporter.consumerSummaryVec hisVec = reporter.consumerHistogramVec } else { - logger.Warnf("The url is not the consumer's or provider's, "+ + logger.Warnf("The url belongs neither the consumer nor the provider, "+ "so the invocation will be ignored. url: %s", url.String()) return } diff --git a/metrics/reporter.go b/metrics/reporter.go index 85ef1dcdf0dad275edecc1f3a85502c1493c1395..9a7094fa62d9c0fa3e6ee0a8ef373f91c28d2c90 100644 --- a/metrics/reporter.go +++ b/metrics/reporter.go @@ -29,7 +29,7 @@ const ( NameSpace = "dubbo" ) -// it will be use to report the invocation's duration +// Reporter will be used to report the invocation's duration type Reporter interface { // report the duration of an invocation Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go index 5ec7db51af0ddfa6e49d3c65910355f0bf2de414..6d1b771bf4108d17372e0ceb5ca818323278afd2 100644 --- a/protocol/dubbo/client.go +++ b/protocol/dubbo/client.go @@ -88,7 +88,7 @@ func init() { rand.Seed(time.Now().UnixNano()) } -// SetClientConf ... +// SetClientConf set dubbo client config. func SetClientConf(c ClientConfig) { clientConf = &c err := clientConf.CheckValidity() @@ -99,7 +99,7 @@ func SetClientConf(c ClientConfig) { setClientGrpool() } -// GetClientConf ... +// GetClientConf get dubbo client config. func GetClientConf() ClientConfig { return *clientConf } @@ -111,7 +111,7 @@ func setClientGrpool() { } } -// Options ... +// Options is option for create dubbo client type Options struct { // connect timeout ConnectTimeout time.Duration @@ -129,7 +129,7 @@ type AsyncCallbackResponse struct { Reply interface{} } -// Client ... +// Client is dubbo protocol client. type Client struct { opts Options conf ClientConfig @@ -139,7 +139,7 @@ type Client struct { pendingResponses *sync.Map } -// NewClient ... +// NewClient create a new Client. func NewClient(opt Options) *Client { switch { @@ -167,7 +167,7 @@ func NewClient(opt Options) *Client { return c } -// Request ... +// Request is dubbo protocol request. type Request struct { addr string svcUrl common.URL @@ -176,7 +176,7 @@ type Request struct { atta map[string]string } -// NewRequest ... +// NewRequest create a new Request. func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, atta map[string]string) *Request { return &Request{ addr: addr, @@ -187,13 +187,13 @@ func NewRequest(addr string, svcUrl common.URL, method string, args interface{}, } } -// Response ... +// Response is dubbo protocol response. type Response struct { reply interface{} atta map[string]string } -// NewResponse ... +// NewResponse create a new Response. func NewResponse(reply interface{}, atta map[string]string) *Response { return &Response{ reply: reply, @@ -201,15 +201,14 @@ func NewResponse(reply interface{}, atta map[string]string) *Response { } } -// CallOneway call one way +// CallOneway call by one way func (c *Client) CallOneway(request *Request) error { return perrors.WithStack(c.call(CT_OneWay, request, NewResponse(nil, nil), nil)) } -// Call if @response is nil, the transport layer will get the response without notify the invoker. +// Call call remoting by two way or one way, if @response.reply is nil, the way of call is one way. func (c *Client) Call(request *Request, response *Response) error { - ct := CT_TwoWay if response.reply == nil { ct = CT_OneWay @@ -218,14 +217,12 @@ func (c *Client) Call(request *Request, response *Response) error { return perrors.WithStack(c.call(ct, request, response, nil)) } -// AsyncCall ... +// AsyncCall call remoting by async with callback. func (c *Client) AsyncCall(request *Request, callback common.AsyncCallback, response *Response) error { - return perrors.WithStack(c.call(CT_TwoWay, request, response, callback)) } func (c *Client) call(ct CallType, request *Request, response *Response, callback common.AsyncCallback) error { - p := &DubboPackage{} p.Service.Path = strings.TrimPrefix(request.svcUrl.Path, "/") p.Service.Interface = request.svcUrl.GetParam(constant.INTERFACE_KEY, "") @@ -293,7 +290,7 @@ func (c *Client) call(ct CallType, request *Request, response *Response, callbac return perrors.WithStack(err) } -// Close ... +// Close close the client pool. func (c *Client) Close() { if c.pool != nil { c.pool.close() diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go index 1e0a73fac1a6cf6d4d102e5f4f6f1ba60fc4102a..744ffa80d6bc65e8526201b8cd327bb12b43caef 100644 --- a/protocol/dubbo/client_test.go +++ b/protocol/dubbo/client_test.go @@ -162,7 +162,7 @@ func InitTest(t *testing.T) (protocol.Protocol, common.URL) { hessian.RegisterPOJO(&User{}) - methods, err := common.ServiceMap.Register("dubbo", &UserProvider{}) + methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "dubbo", &UserProvider{}) assert.NoError(t, err) assert.Equal(t, "GetBigPkg,GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4,GetUser5,GetUser6", methods) diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go index 76416b2baf1e1db516c00d92ecb8ad618bf186bd..1f7d107544a06d0ef83bcb54ff6f03daf2dc517b 100644 --- a/protocol/dubbo/codec.go +++ b/protocol/dubbo/codec.go @@ -65,11 +65,12 @@ type DubboPackage struct { Err error } +// String prints dubbo package detail include header銆乸ath銆乥ody etc. func (p DubboPackage) String() string { return fmt.Sprintf("DubboPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body) } -// Marshal ... +// Marshal encode hessian package. func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { codec := hessian.NewHessianCodec(nil) @@ -81,7 +82,7 @@ func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { return bytes.NewBuffer(pkg), nil } -// Unmarshal ... +// Unmarshal dncode hessian package. func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // fix issue https://github.com/apache/dubbo-go/issues/380 bufLen := buf.Len() @@ -125,7 +126,7 @@ func (p *DubboPackage) Unmarshal(buf *bytes.Buffer, opts ...interface{}) error { // PendingResponse //////////////////////////////////////////// -// PendingResponse ... +// PendingResponse is a pending response. type PendingResponse struct { seq uint64 err error @@ -136,7 +137,7 @@ type PendingResponse struct { done chan struct{} } -// NewPendingResponse ... +// NewPendingResponse create a PendingResponses. func NewPendingResponse() *PendingResponse { return &PendingResponse{ start: time.Now(), @@ -145,7 +146,7 @@ func NewPendingResponse() *PendingResponse { } } -// GetCallResponse ... +// GetCallResponse get AsyncCallbackResponse. func (r PendingResponse) GetCallResponse() common.CallbackResponse { return AsyncCallbackResponse{ Cause: r.err, diff --git a/protocol/dubbo/config.go b/protocol/dubbo/config.go index dbc6989c54780afacef717f1d110833d92967f9f..635d12109add17cfac1056316c9d53817525fd67 100644 --- a/protocol/dubbo/config.go +++ b/protocol/dubbo/config.go @@ -27,7 +27,7 @@ import ( ) type ( - // GettySessionParam ... + // GettySessionParam is session configuration for getty. GettySessionParam struct { CompressEncoding bool `default:"false" yaml:"compress_encoding" json:"compress_encoding,omitempty"` TcpNoDelay bool `default:"true" yaml:"tcp_no_delay" json:"tcp_no_delay,omitempty"` @@ -47,8 +47,7 @@ type ( SessionName string `default:"rpc" yaml:"session_name" json:"session_name,omitempty"` } - // ServerConfig - //Config holds supported types by the multiconfig package + // ServerConfig holds supported types by the multiconfig package ServerConfig struct { // session SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` @@ -64,8 +63,7 @@ type ( GettySessionParam GettySessionParam `required:"true" yaml:"getty_session_param" json:"getty_session_param,omitempty"` } - // ClientConfig - //Config holds supported types by the multiconfig package + // ClientConfig holds supported types by the multiconfig package ClientConfig struct { ReconnectInterval int `default:"0" yaml:"reconnect_interval" json:"reconnect_interval,omitempty"` @@ -94,7 +92,7 @@ type ( } ) -// GetDefaultClientConfig ... +// GetDefaultClientConfig gets client default configuration. func GetDefaultClientConfig() ClientConfig { return ClientConfig{ ReconnectInterval: 0, @@ -122,7 +120,7 @@ func GetDefaultClientConfig() ClientConfig { }} } -// GetDefaultServerConfig ... +// GetDefaultServerConfig gets server default configuration. func GetDefaultServerConfig() ServerConfig { return ServerConfig{ SessionTimeout: "180s", @@ -147,7 +145,7 @@ func GetDefaultServerConfig() ServerConfig { } } -// CheckValidity ... +// CheckValidity confirm getty sessian params. func (c *GettySessionParam) CheckValidity() error { var err error @@ -170,7 +168,7 @@ func (c *GettySessionParam) CheckValidity() error { return nil } -// CheckValidity ... +// CheckValidity confirm client params. func (c *ClientConfig) CheckValidity() error { var err error @@ -192,7 +190,7 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithStack(c.GettySessionParam.CheckValidity()) } -// CheckValidity ... +// CheckValidity confirm server params. func (c *ServerConfig) CheckValidity() error { var err error diff --git a/protocol/dubbo/dubbo_exporter.go b/protocol/dubbo/dubbo_exporter.go index f4cd0cc1234f71bdcf6ce746f01ff3618d820fc5..dd80937c5bdf6718c2047b102115d8c08afcd899 100644 --- a/protocol/dubbo/dubbo_exporter.go +++ b/protocol/dubbo/dubbo_exporter.go @@ -28,23 +28,24 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// DubboExporter ... +// DubboExporter is dubbo service exporter. type DubboExporter struct { protocol.BaseExporter } -// NewDubboExporter ... +// NewDubboExporter get a DubboExporter. func NewDubboExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *DubboExporter { return &DubboExporter{ BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap), } } -// Unexport ... +// Unexport unexport dubbo service exporter. func (de *DubboExporter) Unexport() { serviceId := de.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") + interfaceName := de.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "") de.BaseExporter.Unexport() - err := common.ServiceMap.UnRegister(DUBBO, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, DUBBO, serviceId) if err != nil { logger.Errorf("[DubboExporter.Unexport] error: %v", err) } diff --git a/protocol/dubbo/dubbo_invoker.go b/protocol/dubbo/dubbo_invoker.go index 09c3725710d2a0b821d8e641b0cb7b367189c244..59202d5f49f30acb9e75c4e2f135601285f08e13 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -39,8 +39,9 @@ import ( ) var ( - // ErrNoReply ... - ErrNoReply = perrors.New("request need @response") + // ErrNoReply + ErrNoReply = perrors.New("request need @response") + // ErrDestroyedInvoker ErrDestroyedInvoker = perrors.New("request Destroyed invoker") ) @@ -48,7 +49,7 @@ var ( attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY} ) -// DubboInvoker ... +// DubboInvoker is dubbo client invoker. type DubboInvoker struct { protocol.BaseInvoker client *Client @@ -57,7 +58,7 @@ type DubboInvoker struct { reqNum int64 } -// NewDubboInvoker ... +// NewDubboInvoker create dubbo client invoker. func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker { return &DubboInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), @@ -66,7 +67,7 @@ func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker { } } -// Invoke ... +// Invoke call remoting. func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( err error @@ -122,7 +123,7 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati return &result } -// Destroy ... +// Destroy destroy dubbo client invoker. func (di *DubboInvoker) Destroy() { di.quitOnce.Do(func() { for { diff --git a/protocol/dubbo/dubbo_protocol.go b/protocol/dubbo/dubbo_protocol.go index 355dbc802488338ef4dbdd7290166038b312f183..9eeefd079279d82241da8e21df5edfe77b8003e0 100644 --- a/protocol/dubbo/dubbo_protocol.go +++ b/protocol/dubbo/dubbo_protocol.go @@ -33,7 +33,7 @@ import ( // dubbo protocol constant const ( - // DUBBO ... + // DUBBO is dubbo protocol name DUBBO = "dubbo" ) @@ -45,14 +45,14 @@ var ( dubboProtocol *DubboProtocol ) -// DubboProtocol ... +// DubboProtocol is a dubbo protocol implement. type DubboProtocol struct { protocol.BaseProtocol serverMap map[string]*Server serverLock sync.Mutex } -// NewDubboProtocol ... +// NewDubboProtocol create a dubbo protocol. func NewDubboProtocol() *DubboProtocol { return &DubboProtocol{ BaseProtocol: protocol.NewBaseProtocol(), @@ -60,7 +60,7 @@ func NewDubboProtocol() *DubboProtocol { } } -// Export ... +// Export export dubbo service. func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := url.ServiceKey() @@ -73,7 +73,7 @@ func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { return exporter } -// Refer ... +// Refer create dubbo service reference. func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker { //default requestTimeout var requestTimeout = config.GetConsumerConfig().RequestTimeout @@ -92,7 +92,7 @@ func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker { return invoker } -// Destroy ... +// Destroy destroy dubbo service. func (dp *DubboProtocol) Destroy() { logger.Infof("DubboProtocol destroy.") @@ -124,7 +124,7 @@ func (dp *DubboProtocol) openServer(url common.URL) { } } -// GetProtocol ... +// GetProtocol get a single dubbo protocol. func GetProtocol() protocol.Protocol { if dubboProtocol == nil { dubboProtocol = NewDubboProtocol() diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go index 0251b78a2b0d27a68461c16c284b1af53bcb08aa..4834459390f39912f0683dfe52f65faa72b7c26d 100644 --- a/protocol/dubbo/listener.go +++ b/protocol/dubbo/listener.go @@ -41,10 +41,9 @@ import ( "github.com/apache/dubbo-go/protocol/invocation" ) -// todo: WritePkg_Timeout will entry *.yml +// todo: writePkg_Timeout will entry *.yml const ( - // WritePkg_Timeout ... - WritePkg_Timeout = 5 * time.Second + writePkg_Timeout = 5 * time.Second ) var ( @@ -56,10 +55,12 @@ type rpcSession struct { reqNum int32 } +// AddReqNum adds total request number safely func (s *rpcSession) AddReqNum(num int32) { atomic.AddInt32(&s.reqNum, num) } +// GetReqNum gets total request number safely func (s *rpcSession) GetReqNum() int32 { return atomic.LoadInt32(&s.reqNum) } @@ -68,35 +69,35 @@ func (s *rpcSession) GetReqNum() int32 { // RpcClientHandler // ////////////////////////////////////////// -// RpcClientHandler ... +// RpcClientHandler is handler of RPC Client type RpcClientHandler struct { conn *gettyRPCClient } -// NewRpcClientHandler ... +// NewRpcClientHandler creates RpcClientHandler with @gettyRPCClient func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler { return &RpcClientHandler{conn: client} } -// OnOpen ... +// OnOpen notified when RPC client session opened func (h *RpcClientHandler) OnOpen(session getty.Session) error { h.conn.addSession(session) return nil } -// OnError ... +// OnError notified when RPC client session got any error func (h *RpcClientHandler) OnError(session getty.Session, err error) { - logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) + logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err) h.conn.removeSession(session) } -// OnClose ... +// OnOpen notified when RPC client session closed func (h *RpcClientHandler) OnClose(session getty.Session) { logger.Infof("session{%s} is closing......", session.Stat()) h.conn.removeSession(session) } -// OnMessage ... +// OnMessage notified when RPC client session got any message in connection func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { p, ok := pkg.(*DubboPackage) if !ok { @@ -141,9 +142,9 @@ func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { } } -// OnCron ... +// OnCron notified when RPC client session got any message in cron job func (h *RpcClientHandler) OnCron(session getty.Session) { - rpcSession, err := h.conn.getClientRpcSession(session) + clientRpcSession, err := h.conn.getClientRpcSession(session) if err != nil { logger.Errorf("client.getClientSession(session{%s}) = error{%v}", session.Stat(), perrors.WithStack(err)) @@ -151,7 +152,7 @@ func (h *RpcClientHandler) OnCron(session getty.Session) { } if h.conn.pool.rpcClient.conf.sessionTimeout.Nanoseconds() < time.Since(session.GetActive()).Nanoseconds() { logger.Warnf("session{%s} timeout{%s}, reqNum{%d}", - session.Stat(), time.Since(session.GetActive()).String(), rpcSession.reqNum) + session.Stat(), time.Since(session.GetActive()).String(), clientRpcSession.reqNum) h.conn.removeSession(session) // -> h.conn.close() -> h.conn.pool.remove(h.conn) return } @@ -163,7 +164,7 @@ func (h *RpcClientHandler) OnCron(session getty.Session) { // RpcServerHandler // ////////////////////////////////////////// -// RpcServerHandler ... +// RpcServerHandler is handler of RPC Server type RpcServerHandler struct { maxSessionNum int sessionTimeout time.Duration @@ -171,7 +172,7 @@ type RpcServerHandler struct { rwlock sync.RWMutex } -// NewRpcServerHandler ... +// NewRpcServerHandler creates RpcServerHandler with @maxSessionNum and @sessionTimeout func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler { return &RpcServerHandler{ maxSessionNum: maxSessionNum, @@ -180,7 +181,7 @@ func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcSe } } -// OnOpen ... +// OnOpen notified when RPC server session opened func (h *RpcServerHandler) OnOpen(session getty.Session) error { var err error h.rwlock.RLock() @@ -199,15 +200,15 @@ func (h *RpcServerHandler) OnOpen(session getty.Session) error { return nil } -// OnError ... +// OnError notified when RPC server session got any error func (h *RpcServerHandler) OnError(session getty.Session, err error) { - logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) + logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err) h.rwlock.Lock() delete(h.sessionMap, session) h.rwlock.Unlock() } -// OnClose ... +// OnOpen notified when RPC server session closed func (h *RpcServerHandler) OnClose(session getty.Session) { logger.Infof("session{%s} is closing......", session.Stat()) h.rwlock.Lock() @@ -215,7 +216,7 @@ func (h *RpcServerHandler) OnClose(session getty.Session) { h.rwlock.Unlock() } -// OnMessage ... +// OnMessage notified when RPC server session got any message in connection func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { h.rwlock.Lock() if _, ok := h.sessionMap[session]; ok { @@ -306,7 +307,7 @@ func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { reply(session, p, hessian.PackageResponse) } -// OnCron ... +// OnCron notified when RPC server session got any message in cron job func (h *RpcServerHandler) OnCron(session getty.Session) { var ( flag bool @@ -363,7 +364,7 @@ func reply(session getty.Session, req *DubboPackage, tp hessian.PackageType) { resp.Body = nil } - if err := session.WritePkg(resp, WritePkg_Timeout); err != nil { + if err := session.WritePkg(resp, writePkg_Timeout); err != nil { logger.Errorf("WritePkg error: %#v, %#v", perrors.WithStack(err), req.Header) } } diff --git a/protocol/dubbo/pool.go b/protocol/dubbo/pool.go index 918514c2676cfc69336a9f53e6d16d7f23cf7dca..f0bd09ba7c3392dd1dbe10306c7c70cc0eab8ccb 100644 --- a/protocol/dubbo/pool.go +++ b/protocol/dubbo/pool.go @@ -219,25 +219,25 @@ func (c *gettyRPCClient) updateSession(session getty.Session) { func (c *gettyRPCClient) getClientRpcSession(session getty.Session) (rpcSession, error) { var ( - err error - rpcSession rpcSession + err error + rpcClientSession rpcSession ) c.lock.RLock() defer c.lock.RUnlock() if c.sessions == nil { - return rpcSession, errClientClosed + return rpcClientSession, errClientClosed } err = errSessionNotExist for _, s := range c.sessions { if s.session == session { - rpcSession = *s + rpcClientSession = *s err = nil break } } - return rpcSession, perrors.WithStack(err) + return rpcClientSession, perrors.WithStack(err) } func (c *gettyRPCClient) isAvailable() bool { @@ -319,7 +319,8 @@ func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPC conn, err := p.get() if err == nil && conn == nil { // create new conn - rpcClientConn, err := newGettyRPCClientConn(p, protocol, addr) + var rpcClientConn *gettyRPCClient + rpcClientConn, err = newGettyRPCClientConn(p, protocol, addr) return rpcClientConn, perrors.WithStack(err) } return conn, perrors.WithStack(err) diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go index b5c4f509190dbdc85825ad424656240b234786df..9cc7ea25cdfa6152d632f278b33285d7d38f47c9 100644 --- a/protocol/dubbo/readwriter.go +++ b/protocol/dubbo/readwriter.go @@ -38,16 +38,17 @@ import ( // RpcClientPackageHandler //////////////////////////////////////////// -// RpcClientPackageHandler ... +// RpcClientPackageHandler handle package for client in getty. type RpcClientPackageHandler struct { client *Client } -// NewRpcClientPackageHandler ... +// NewRpcClientPackageHandler create a RpcClientPackageHandler. func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler { return &RpcClientPackageHandler{client: client} } +// Read decode @data to DubboPackage. func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { pkg := &DubboPackage{} @@ -72,6 +73,7 @@ func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil } +// Write encode @pkg. func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { req, ok := pkg.(*DubboPackage) if !ok { @@ -96,9 +98,10 @@ var ( rpcServerPkgHandler = &RpcServerPackageHandler{} ) -// RpcServerPackageHandler ... +// RpcServerPackageHandler handle package for server in getty. type RpcServerPackageHandler struct{} +// Read decode @data to DubboPackage. func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { pkg := &DubboPackage{ Body: make([]interface{}, 7), @@ -169,6 +172,7 @@ func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil } +// Write encode @pkg. func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { res, ok := pkg.(*DubboPackage) if !ok { diff --git a/protocol/dubbo/server.go b/protocol/dubbo/server.go index bd2b37b7a9f055745e183524d19a442af03360f4..8de353a0b372785e89585f4bf2b00b2c42540a2b 100644 --- a/protocol/dubbo/server.go +++ b/protocol/dubbo/server.go @@ -71,10 +71,10 @@ func init() { if err := srvConf.CheckValidity(); err != nil { panic(err) } - SetServerGrpool() + setServerGrpool() } -// SetServerConfig ... +// SetServerConfig set dubbo server config. func SetServerConfig(s ServerConfig) { srvConf = &s err := srvConf.CheckValidity() @@ -82,30 +82,29 @@ func SetServerConfig(s ServerConfig) { logger.Warnf("[ServerConfig CheckValidity] error: %v", err) return } - SetServerGrpool() + setServerGrpool() } -// GetServerConfig ... +// GetServerConfig get dubbo server config. func GetServerConfig() ServerConfig { return *srvConf } -// SetServerGrpool ... -func SetServerGrpool() { +func setServerGrpool() { if srvConf.GrPoolSize > 1 { srvGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(srvConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(srvConf.QueueLen), gxsync.WithTaskPoolTaskQueueNumber(srvConf.QueueNumber)) } } -// Server ... +// Server is dubbo protocol server. type Server struct { conf ServerConfig tcpServer getty.Server rpcHandler *RpcServerHandler } -// NewServer ... +// NewServer create a new Server. func NewServer() *Server { s := &Server{ @@ -156,7 +155,7 @@ func (s *Server) newSession(session getty.Session) error { return nil } -// Start ... +// Start start dubbo server. func (s *Server) Start(url common.URL) { var ( addr string @@ -173,7 +172,7 @@ func (s *Server) Start(url common.URL) { } -// Stop ... +// Stop stop dubbo server. func (s *Server) Stop() { s.tcpServer.Close() } diff --git a/protocol/grpc/client.go b/protocol/grpc/client.go index 6026f0991b926fd38de8aef3774e46b001820edd..a0ab0be80cc905115e675c1c4dea2b1c748f6c09 100644 --- a/protocol/grpc/client.go +++ b/protocol/grpc/client.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc @@ -25,27 +25,78 @@ import ( "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" "github.com/opentracing/opentracing-go" "google.golang.org/grpc" + "gopkg.in/yaml.v2" ) 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" ) -// Client ... +var ( + clientConf *ClientConfig +) + +func init() { + // load clientconfig from consumer_config + consumerConfig := config.GetConsumerConfig() + + clientConfig := GetClientConfig() + clientConf = &clientConfig + + // check client config and decide whether to use the default config + defer func() { + if clientConf == nil || len(clientConf.ContentSubType) == 0 { + defaultClientConfig := GetDefaultClientConfig() + clientConf = &defaultClientConfig + } + if err := clientConf.Validate(); err != nil { + panic(err) + } + }() + + if consumerConfig.ApplicationConfig == nil { + return + } + protocolConf := config.GetConsumerConfig().ProtocolConf + + if protocolConf == nil { + logger.Info("protocol_conf default use dubbo config") + } else { + grpcConf := protocolConf.(map[interface{}]interface{})[GRPC] + if grpcConf == nil { + logger.Warnf("grpcConf is nil") + return + } + grpcConfByte, err := yaml.Marshal(grpcConf) + if err != nil { + panic(err) + } + err = yaml.Unmarshal(grpcConfByte, clientConf) + if err != nil { + panic(err) + } + } + +} + +// Client is gRPC client include client connection and invoker type Client struct { *grpc.ClientConn invoker reflect.Value } -// NewClient ... +// NewClient creates a new gRPC client. func NewClient(url common.URL) *Client { // if global trace instance was set , it means trace function enabled. If not , will return Nooptracer tracer := opentracing.GlobalTracer() - conn, err := grpc.Dial(url.Location, grpc.WithInsecure(), grpc.WithBlock(), - grpc.WithUnaryInterceptor( - otgrpc.OpenTracingClientInterceptor(tracer, otgrpc.LogPayloads()))) + dailOpts := make([]grpc.DialOption, 0, 4) + dailOpts = append(dailOpts, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithUnaryInterceptor( + otgrpc.OpenTracingClientInterceptor(tracer, otgrpc.LogPayloads())), + grpc.WithDefaultCallOptions(grpc.CallContentSubtype(clientConf.ContentSubType))) + conn, err := grpc.Dial(url.Location, dailOpts...) if err != nil { panic(err) } diff --git a/protocol/grpc/client_test.go b/protocol/grpc/client_test.go index 56ec766f70da93bcddbcff13667a34c39deffe06..099f03e429ec83dd01941f6add68c751cf6dd7b0 100644 --- a/protocol/grpc/client_test.go +++ b/protocol/grpc/client_test.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc diff --git a/protocol/grpc/codec.go b/protocol/grpc/codec.go new file mode 100644 index 0000000000000000000000000000000000000000..7235a3941baefcdce5964ae21dbdbfe9d6ca9cc8 --- /dev/null +++ b/protocol/grpc/codec.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 grpc + +import ( + "bytes" + "encoding/json" +) + +import ( + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "google.golang.org/grpc/encoding" +) + +const ( + codecJson = "json" + codecProto = "proto" +) + +func init() { + encoding.RegisterCodec(grpcJson{ + Marshaler: jsonpb.Marshaler{ + EmitDefaults: true, + OrigName: true, + }, + }) +} + +type grpcJson struct { + jsonpb.Marshaler + jsonpb.Unmarshaler +} + +// Name implements grpc encoding package Codec interface method, +// returns the name of the Codec implementation. +func (_ grpcJson) Name() string { + return codecJson +} + +// Marshal implements grpc encoding package Codec interface method,returns the wire format of v. +func (j grpcJson) Marshal(v interface{}) (out []byte, err error) { + if pm, ok := v.(proto.Message); ok { + b := new(bytes.Buffer) + err := j.Marshaler.Marshal(b, pm) + if err != nil { + return nil, err + } + return b.Bytes(), nil + } + return json.Marshal(v) +} + +// Unmarshal implements grpc encoding package Codec interface method,Unmarshal parses the wire format into v. +func (j grpcJson) Unmarshal(data []byte, v interface{}) (err error) { + if pm, ok := v.(proto.Message); ok { + b := bytes.NewBuffer(data) + return j.Unmarshaler.Unmarshal(b, pm) + } + return json.Unmarshal(data, v) +} diff --git a/protocol/grpc/common_test.go b/protocol/grpc/common_test.go index 3d0823b1061a61cfa391358e270c8b9081e9031c..33c2fc617d52795d13d9b4fc02054ef5a79d0934 100644 --- a/protocol/grpc/common_test.go +++ b/protocol/grpc/common_test.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc diff --git a/protocol/grpc/config.go b/protocol/grpc/config.go new file mode 100644 index 0000000000000000000000000000000000000000..e8a9baac8999a009bab435a5bd4e70d102f77b56 --- /dev/null +++ b/protocol/grpc/config.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 grpc + +import ( + perrors "github.com/pkg/errors" +) + +type ( + // ServerConfig currently is empty struct,for future expansion + ServerConfig struct { + } + + // ClientConfig wrap client call parameters + ClientConfig struct { + // content type, more information refer by https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests + ContentSubType string `default:"proto" yaml:"content_sub_type" json:"content_sub_type,omitempty"` + } +) + +// GetDefaultClientConfig return grpc client default call options +func GetDefaultClientConfig() ClientConfig { + return ClientConfig{ + ContentSubType: codecProto, + } +} + +// GetDefaultServerConfig currently return empty struct,for future expansion +func GetDefaultServerConfig() ServerConfig { + return ServerConfig{} +} + +// GetClientConfig return grpc client custom call options +func GetClientConfig() ClientConfig { + return ClientConfig{} +} + +// Validate check if custom config encoding is supported in dubbo grpc +func (c *ClientConfig) Validate() error { + if c.ContentSubType != codecJson && c.ContentSubType != codecProto { + return perrors.Errorf(" dubbo-go grpc codec currently only support proto銆乯son, %s isn't supported,"+ + " please check protocol content_sub_type config", c.ContentSubType) + } + return nil +} + +// Validate currently return empty struct,for future expansion +func (c *ServerConfig) Validate() error { + return nil +} diff --git a/protocol/grpc/grpc_exporter.go b/protocol/grpc/grpc_exporter.go index 3c38ef974ca22a582ce83102718d01a8edd4258f..79962b59e29bb0e3aeb58776f6c26abc2e6832de 100644 --- a/protocol/grpc/grpc_exporter.go +++ b/protocol/grpc/grpc_exporter.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc @@ -33,18 +33,19 @@ type GrpcExporter struct { *protocol.BaseExporter } -// NewGrpcExporter ... +// NewGrpcExporter creates a new gRPC exporter func NewGrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *GrpcExporter { return &GrpcExporter{ BaseExporter: protocol.NewBaseExporter(key, invoker, exporterMap), } } -// Unexport ... +// Unexport and unregister gRPC service from registry and memory. func (gg *GrpcExporter) Unexport() { serviceId := gg.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") + interfaceName := gg.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "") gg.BaseExporter.Unexport() - err := common.ServiceMap.UnRegister(GRPC, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, GRPC, serviceId) if err != nil { logger.Errorf("[GrpcExporter.Unexport] error: %v", err) } diff --git a/protocol/grpc/grpc_invoker.go b/protocol/grpc/grpc_invoker.go index 26bc86f3aa46c8048b16284bd61cb5d9fb4664f9..e150d05e6fb39890bd3e355f4042b4ef34db42ed 100644 --- a/protocol/grpc/grpc_invoker.go +++ b/protocol/grpc/grpc_invoker.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc @@ -35,8 +35,7 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// ErrNoReply ... -var ErrNoReply = errors.New("request need @response") +var errNoReply = errors.New("request need @response") // GrpcInvoker ... type GrpcInvoker struct { @@ -60,7 +59,7 @@ func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio ) if invocation.Reply() == nil { - result.Err = ErrNoReply + result.Err = errNoReply } in := []reflect.Value{} diff --git a/protocol/grpc/grpc_invoker_test.go b/protocol/grpc/grpc_invoker_test.go index 368c1392ec03af310f93e8fc2173b8354975c99e..3054ada13340a9c9cc038a63d89c45ced9ec7ac7 100644 --- a/protocol/grpc/grpc_invoker_test.go +++ b/protocol/grpc/grpc_invoker_test.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc diff --git a/protocol/grpc/grpc_protocol.go b/protocol/grpc/grpc_protocol.go index 0f5625c152cc366289143b8a29d11cafb513b2f2..68594a4b35921b6e3b1d59d404ed163025d57a81 100644 --- a/protocol/grpc/grpc_protocol.go +++ b/protocol/grpc/grpc_protocol.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc @@ -39,14 +39,14 @@ func init() { var grpcProtocol *GrpcProtocol -// GrpcProtocol ... +// GrpcProtocol is gRPC protocol type GrpcProtocol struct { protocol.BaseProtocol serverMap map[string]*Server serverLock sync.Mutex } -// NewGRPCProtocol ... +// NewGRPCProtocol creates new gRPC protocol func NewGRPCProtocol() *GrpcProtocol { return &GrpcProtocol{ BaseProtocol: protocol.NewBaseProtocol(), @@ -54,7 +54,7 @@ func NewGRPCProtocol() *GrpcProtocol { } } -// Export ... +// Export gRPC service for remote invocation func (gp *GrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := url.ServiceKey() @@ -84,7 +84,7 @@ func (gp *GrpcProtocol) openServer(url common.URL) { } } -// Refer ... +// Refer a remote gRPC service func (gp *GrpcProtocol) Refer(url common.URL) protocol.Invoker { invoker := NewGrpcInvoker(url, NewClient(url)) gp.SetInvokers(invoker) @@ -92,7 +92,7 @@ func (gp *GrpcProtocol) Refer(url common.URL) protocol.Invoker { return invoker } -// Destroy ... +// Destroy will destroy gRPC all invoker and exporter, so it only is called once. func (gp *GrpcProtocol) Destroy() { logger.Infof("GrpcProtocol destroy.") @@ -104,7 +104,7 @@ func (gp *GrpcProtocol) Destroy() { } } -// GetProtocol ... +// GetProtocol gets gRPC protocol , will create if null. func GetProtocol() protocol.Protocol { if grpcProtocol == nil { grpcProtocol = NewGRPCProtocol() diff --git a/protocol/grpc/grpc_protocol_test.go b/protocol/grpc/grpc_protocol_test.go index d0206a0fd953e40a478c26a2298f4889d8f72771..d028f8ef4285b0183e6e0b5b32deede59ce5c531 100644 --- a/protocol/grpc/grpc_protocol_test.go +++ b/protocol/grpc/grpc_protocol_test.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc diff --git a/protocol/grpc/internal/client.go b/protocol/grpc/internal/client.go index d236e3046a90e9179fba07a0be5edb07f8c2a3e8..3ce0f570b7eb3424fa85a868931b1b3cebe362eb 100644 --- a/protocol/grpc/internal/client.go +++ b/protocol/grpc/internal/client.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package internal diff --git a/protocol/grpc/internal/doc.go b/protocol/grpc/internal/doc.go index f2ef2ebd5e41980e1e1f1b0071ca7bb3885539f7..b70fc24e728acb85364a8c314f65a7ec997cda58 100644 --- a/protocol/grpc/internal/doc.go +++ b/protocol/grpc/internal/doc.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // just for test, never use internal for production. package internal diff --git a/protocol/grpc/internal/helloworld.pb.go b/protocol/grpc/internal/helloworld.pb.go index 79b74ac65011208ae74f989cf86e4e6f9f446015..82537f553bdd8ed74970627c217091b6a9de637b 100644 --- a/protocol/grpc/internal/helloworld.pb.go +++ b/protocol/grpc/internal/helloworld.pb.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // Code generated by protoc-gen-go. DO NOT EDIT. // source: helloworld.proto diff --git a/protocol/grpc/internal/server.go b/protocol/grpc/internal/server.go index a0759f757dc44153e7f09b726db5e66176796c96..f7b99fa0ba557fa002321b5d2435d17cf792dd38 100644 --- a/protocol/grpc/internal/server.go +++ b/protocol/grpc/internal/server.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package internal @@ -42,7 +42,7 @@ func (s *server) SayHello(ctx context.Context, in *HelloRequest) (*HelloReply, e return &HelloReply{Message: "Hello " + in.GetName()}, nil } -// InitGrpcServer ... +// InitGrpcServer creates global gRPC server. func InitGrpcServer() { port := ":30000" @@ -57,7 +57,7 @@ func InitGrpcServer() { } } -// ShutdownGrpcServer ... +// ShutdownGrpcServer shuts down gRPC server gracefully func ShutdownGrpcServer() { if s == nil { return diff --git a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.pb.go b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.pb.go index f5d3a49b0916050fc6b2e6373fde0b70df0a1c31..702391b299402cf7f1cefbcc785fd10ea9f1634d 100644 --- a/protocol/grpc/protoc-gen-dubbo/examples/helloworld.pb.go +++ b/protocol/grpc/protoc-gen-dubbo/examples/helloworld.pb.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // Code generated by protoc-gen-go. DO NOT EDIT. // source: helloworld.proto diff --git a/protocol/grpc/protoc-gen-dubbo/main.go b/protocol/grpc/protoc-gen-dubbo/main.go index b2f0e82f74a4d3c1a7013714cd18d83562baff71..fbcfa6f9d492afa8bc7848c733358b3cf7223e99 100644 --- a/protocol/grpc/protoc-gen-dubbo/main.go +++ b/protocol/grpc/protoc-gen-dubbo/main.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package main diff --git a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/doc.go b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/doc.go index 064c738a53d2200223b0ca81aca77358afad032b..76fbf647249c57464204867398299b51c65bb846 100644 --- a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/doc.go +++ b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/doc.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // Package dubbo plugin for protobuf. package dubbo diff --git a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go index e84a7d0cc96887cf728f499c28c26f061ed1ccdf..1af4fafdc606783e937ede63f99e5a08f0b2419e 100644 --- a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go +++ b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dubbo @@ -107,7 +107,6 @@ func (g *dubboGrpc) GenerateImports(file *generator.FileDescriptor) { g.P(`dgrpc "github.com/apache/dubbo-go/protocol/grpc"`) g.P(`"github.com/apache/dubbo-go/protocol/invocation"`) g.P(`"github.com/apache/dubbo-go/protocol"`) - g.P(`"github.com/apache/dubbo-go/config"`) g.P(` ) `) } @@ -266,7 +265,7 @@ func (g *dubboGrpc) generateServerMethod(servName, fullServName string, method * g.P(`invo := invocation.NewRPCInvocation("`, methName, `", args, nil)`) g.P("if interceptor == nil {") - g.P("result := base.GetProxyImpl().Invoke(invo)") + g.P("result := base.GetProxyImpl().Invoke(context.Background(), invo)") g.P("return result.Result(), result.Error()") g.P("}") @@ -276,7 +275,7 @@ func (g *dubboGrpc) generateServerMethod(servName, fullServName string, method * g.P("}") g.P("handler := func(ctx ", contextPkg, ".Context, req interface{}) (interface{}, error) {") - g.P("result := base.GetProxyImpl().Invoke(invo)") + g.P("result := base.GetProxyImpl().Invoke(context.Background(), invo)") g.P("return result.Result(), result.Error()") g.P("}") diff --git a/protocol/grpc/server.go b/protocol/grpc/server.go index cc184bf3cff83e6ed57bc41cba49c184860233dd..4017b65dd5c35ef19795b390e40d7afff6699306 100644 --- a/protocol/grpc/server.go +++ b/protocol/grpc/server.go @@ -1,19 +1,19 @@ /* -Licensed to the Apache Software Foundation (ASF) under one or more -contributor license agreements. See the NOTICE file distributed with -this work for additional information regarding copyright ownership. -The ASF licenses this file to You under the Apache License, Version 2.0 -(the "License"); you may not use this file except in compliance with -the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package grpc @@ -37,24 +37,27 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// Server ... +// Server is a gRPC server type Server struct { grpcServer *grpc.Server } -// NewServer ... +// NewServer creates a new server func NewServer() *Server { return &Server{} } -// DubboGrpcService ... +// DubboGrpcService is gRPC service type DubboGrpcService interface { + // SetProxyImpl sets proxy. SetProxyImpl(impl protocol.Invoker) + // GetProxyImpl gets proxy. GetProxyImpl() protocol.Invoker + // ServiceDesc gets an RPC service's specification. ServiceDesc() *grpc.ServiceDesc } -// Start ... +// Start gRPC server with @url func (s *Server) Start(url common.URL) { var ( addr string @@ -106,7 +109,7 @@ func (s *Server) Start(url common.URL) { }() } -// Stop ... +// Stop gRPC server func (s *Server) Stop() { s.grpcServer.Stop() } diff --git a/protocol/invocation.go b/protocol/invocation.go index f32f2c3449ac063ecb89952bd4653312a07a3df4..ba5949794c0120874ebdf31cfb1fd9c7d8ac08e4 100644 --- a/protocol/invocation.go +++ b/protocol/invocation.go @@ -21,14 +21,26 @@ import ( "reflect" ) -// Invocation ... +// Invocation is a invocation for each remote method. type Invocation interface { + // MethodName gets invocation method name. MethodName() string + // ParameterTypes gets invocation parameter types. ParameterTypes() []reflect.Type + // ParameterValues gets invocation parameter values. ParameterValues() []reflect.Value + // Arguments gets arguments. Arguments() []interface{} + // Reply gets response of request Reply() interface{} + // Attachments gets all attachments Attachments() map[string]string + // AttachmentsByKey gets attachment by key , if nil then return default value AttachmentsByKey(string, string) string + // Attributes refers to dubbo 2.7.6. It is different from attachment. It is used in internal process. + Attributes() map[string]interface{} + // AttributeByKey gets attribute by key , if nil then return default value + AttributeByKey(string, interface{}) interface{} + // Invoker gets the invoker in current context. Invoker() Invoker } diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index b207fd0b0cc4eb7de8409a8c46c6fc9ef0baa5c7..7f2cede9295e24c521de017ab3a1da7eaad250ab 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -31,7 +31,7 @@ import ( // /////////////////////////// // todo: is it necessary to separate fields of consumer(provider) from RPCInvocation -// RPCInvocation ... +// nolint type RPCInvocation struct { methodName string parameterTypes []reflect.Type @@ -40,64 +40,70 @@ type RPCInvocation struct { reply interface{} callBack interface{} attachments map[string]string - invoker protocol.Invoker - lock sync.RWMutex + // Refer to dubbo 2.7.6. It is different from attachment. It is used in internal process. + attributes map[string]interface{} + invoker protocol.Invoker + lock sync.RWMutex } -// NewRPCInvocation ... +// NewRPCInvocation creates a RPC invocation. func NewRPCInvocation(methodName string, arguments []interface{}, attachments map[string]string) *RPCInvocation { return &RPCInvocation{ methodName: methodName, arguments: arguments, attachments: attachments, + attributes: make(map[string]interface{}, 8), } } -// NewRPCInvocationWithOptions ... +// NewRPCInvocationWithOptions creates a RPC invocation with @opts. func NewRPCInvocationWithOptions(opts ...option) *RPCInvocation { invo := &RPCInvocation{} for _, opt := range opts { opt(invo) } + if invo.attributes == nil { + invo.attributes = make(map[string]interface{}) + } return invo } -// MethodName ... +// MethodName gets RPC invocation method name. func (r *RPCInvocation) MethodName() string { return r.methodName } -// ParameterTypes ... +// ParameterTypes gets RPC invocation parameter types. func (r *RPCInvocation) ParameterTypes() []reflect.Type { return r.parameterTypes } -// ParameterValues ... +// ParameterValues gets RPC invocation parameter values. func (r *RPCInvocation) ParameterValues() []reflect.Value { return r.parameterValues } -// Arguments ... +// Arguments gets RPC arguments. func (r *RPCInvocation) Arguments() []interface{} { return r.arguments } -// Reply ... +// Reply gets response of RPC request. func (r *RPCInvocation) Reply() interface{} { return r.reply } -// SetReply ... +// SetReply sets response of RPC request. func (r *RPCInvocation) SetReply(reply interface{}) { r.reply = reply } -// Attachments ... +// Attachments gets all attachments of RPC. func (r *RPCInvocation) Attachments() map[string]string { return r.attachments } -// AttachmentsByKey ... +// AttachmentsByKey gets RPC attachment by key , if nil then return default value. func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string { r.lock.RLock() defer r.lock.RUnlock() @@ -111,7 +117,23 @@ func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string return defaultValue } -// SetAttachments ... +// Attributes gets all attributes of RPC. +func (r *RPCInvocation) Attributes() map[string]interface{} { + return r.attributes +} + +// AttributeByKey gets attribute by @key. If it is not exist, it will return default value. +func (r *RPCInvocation) AttributeByKey(key string, defaultValue interface{}) interface{} { + r.lock.RLock() + defer r.lock.RUnlock() + value, ok := r.attributes[key] + if ok { + return value + } + return defaultValue +} + +// SetAttachments sets attribute by @key and @value. func (r *RPCInvocation) SetAttachments(key string, value string) { r.lock.Lock() defer r.lock.Unlock() @@ -121,22 +143,24 @@ func (r *RPCInvocation) SetAttachments(key string, value string) { r.attachments[key] = value } -// Invoker ... -func (r *RPCInvocation) Invoker() protocol.Invoker { - return r.invoker +// SetAttribute sets attribute by @key and @value. +func (r *RPCInvocation) SetAttribute(key string, value interface{}) { + r.lock.Lock() + defer r.lock.Unlock() + r.attributes[key] = value } -// SetInvoker ... -func (r *RPCInvocation) SetInvoker() protocol.Invoker { +// Invoker gets the invoker in current context. +func (r *RPCInvocation) Invoker() protocol.Invoker { return r.invoker } -// CallBack ... +// CallBack sets RPC callback method. func (r *RPCInvocation) CallBack() interface{} { return r.callBack } -// SetCallBack ... +// SetCallBack sets RPC callback method. func (r *RPCInvocation) SetCallBack(c interface{}) { r.callBack = c } @@ -147,56 +171,56 @@ func (r *RPCInvocation) SetCallBack(c interface{}) { type option func(invo *RPCInvocation) -// WithMethodName ... +// WithMethodName creates option with @methodName. func WithMethodName(methodName string) option { return func(invo *RPCInvocation) { invo.methodName = methodName } } -// WithParameterTypes ... +// WithParameterTypes creates option with @parameterTypes. func WithParameterTypes(parameterTypes []reflect.Type) option { return func(invo *RPCInvocation) { invo.parameterTypes = parameterTypes } } -// WithParameterValues ... +// WithParameterValues creates option with @parameterValues func WithParameterValues(parameterValues []reflect.Value) option { return func(invo *RPCInvocation) { invo.parameterValues = parameterValues } } -// WithArguments ... +// WithArguments creates option with @arguments function. func WithArguments(arguments []interface{}) option { return func(invo *RPCInvocation) { invo.arguments = arguments } } -// WithReply ... +// WithReply creates option with @reply function. func WithReply(reply interface{}) option { return func(invo *RPCInvocation) { invo.reply = reply } } -// WithCallBack ... +// WithCallBack creates option with @callback function. func WithCallBack(callBack interface{}) option { return func(invo *RPCInvocation) { invo.callBack = callBack } } -// WithAttachments ... +// WithAttachments creates option with @attachments. func WithAttachments(attachments map[string]string) option { return func(invo *RPCInvocation) { invo.attachments = attachments } } -// WithInvoker ... +// WithInvoker creates option with @invoker. func WithInvoker(invoker protocol.Invoker) option { return func(invo *RPCInvocation) { invo.invoker = invoker diff --git a/protocol/invoker.go b/protocol/invoker.go index bb71bab1cfa2ede7fb035912ae996f9adb7411e0..3ca370479cbe2255f26628b855b11b07396f1b6d 100644 --- a/protocol/invoker.go +++ b/protocol/invoker.go @@ -31,6 +31,7 @@ import ( // Extension - Invoker type Invoker interface { common.Node + // Invoke the invocation and return result. Invoke(context.Context, Invocation) Result } @@ -38,14 +39,14 @@ type Invoker interface { // base invoker ///////////////////////////// -// BaseInvoker ... +// BaseInvoker provides default invoker implement type BaseInvoker struct { url common.URL available bool destroyed bool } -// NewBaseInvoker ... +// NewBaseInvoker creates a new BaseInvoker func NewBaseInvoker(url common.URL) *BaseInvoker { return &BaseInvoker{ url: url, @@ -54,27 +55,27 @@ func NewBaseInvoker(url common.URL) *BaseInvoker { } } -// GetUrl ... +// GetUrl gets base invoker URL func (bi *BaseInvoker) GetUrl() common.URL { return bi.url } -// IsAvailable ... +// IsAvailable gets available flag func (bi *BaseInvoker) IsAvailable() bool { return bi.available } -// IsDestroyed ... +// IsDestroyed gets destroyed flag func (bi *BaseInvoker) IsDestroyed() bool { return bi.destroyed } -// Invoke ... +// Invoke provides default invoker implement func (bi *BaseInvoker) Invoke(context context.Context, invocation Invocation) Result { return &RPCResult{} } -// Destroy ... +// Destroy changes available and destroyed flag func (bi *BaseInvoker) Destroy() { logger.Infof("Destroy invoker: %s", bi.GetUrl().String()) bi.destroyed = true diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go index ba7197dbc857c2ed7acda1a9f246a5b826e86915..5fca66d99399b2974f858cbedb31d9615a303637 100644 --- a/protocol/jsonrpc/http.go +++ b/protocol/jsonrpc/http.go @@ -63,7 +63,7 @@ type Request struct { // HTTP Client // //////////////////////////////////////////// -// HTTPOptions ... +// HTTPOptions is a HTTP option include HandshakeTimeout and HTTPTimeout. type HTTPOptions struct { HandshakeTimeout time.Duration HTTPTimeout time.Duration @@ -74,13 +74,13 @@ var defaultHTTPOptions = HTTPOptions{ HTTPTimeout: 3 * time.Second, } -// HTTPClient ... +// HTTPClient is a HTTP client ,include ID and options. type HTTPClient struct { ID int64 options HTTPOptions } -// NewHTTPClient ... +// NewHTTPClient creates a new HTTP client with HTTPOptions. func NewHTTPClient(opt *HTTPOptions) *HTTPClient { if opt == nil { opt = &defaultHTTPOptions @@ -100,7 +100,7 @@ func NewHTTPClient(opt *HTTPOptions) *HTTPClient { } } -// NewRequest ... +// NewRequest creates a new HTTP request with @service ,@method and @arguments. func (c *HTTPClient) NewRequest(service common.URL, method string, args interface{}) *Request { return &Request{ @@ -114,7 +114,7 @@ func (c *HTTPClient) NewRequest(service common.URL, method string, args interfac } } -// Call ... +// Call makes a HTTP call with @ctx , @service ,@req and @rsp func (c *HTTPClient) Call(ctx context.Context, service common.URL, req *Request, rsp interface{}) error { // header httpHeader := http.Header{} @@ -172,7 +172,7 @@ func (c *HTTPClient) Do(addr, path string, httpHeader http.Header, body []byte) httpReq.Close = true reqBuf := bytes.NewBuffer(make([]byte, 0)) - if err := httpReq.Write(reqBuf); err != nil { + if err = httpReq.Write(reqBuf); err != nil { return nil, perrors.WithStack(err) } @@ -191,7 +191,7 @@ func (c *HTTPClient) Do(addr, path string, httpHeader http.Header, body []byte) } setNetConnTimeout(tcpConn, c.options.HTTPTimeout) - if _, err := reqBuf.WriteTo(tcpConn); err != nil { + if _, err = reqBuf.WriteTo(tcpConn); err != nil { return nil, perrors.WithStack(err) } diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go index 0cb88b36a8f330059906eb70417b6d4841020c38..f8480bf32e15ad428209eedb72757a299455eb20 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -50,7 +50,7 @@ type ( func TestHTTPClient_Call(t *testing.T) { - methods, err := common.ServiceMap.Register("jsonrpc", &UserProvider{}) + methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "jsonrpc", &UserProvider{}) assert.NoError(t, err) assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4", methods) diff --git a/protocol/jsonrpc/json.go b/protocol/jsonrpc/json.go index d1c2a858b4e4223ac32fc1160b56f6ee1862c8ce..389ead9c1a530742c872a238d89b2df5ae3462ca 100644 --- a/protocol/jsonrpc/json.go +++ b/protocol/jsonrpc/json.go @@ -37,7 +37,7 @@ const ( VERSION = "2.0" ) -// CodecData ... +// CodecData is codec data for json RPC. type CodecData struct { ID int64 Method string @@ -64,11 +64,12 @@ type Error struct { Data interface{} `json:"data,omitempty"` } +// Error decodes response error for a string. func (e *Error) Error() string { buf, err := json.Marshal(e) if err != nil { - msg, err := json.Marshal(err.Error()) - if err != nil { + msg, retryErr := json.Marshal(err.Error()) + if retryErr != nil { msg = []byte("jsonrpc2.Error: json.Marshal failed") } return fmt.Sprintf(`{"code":%d,"message":%s}`, -32001, string(msg)) @@ -114,6 +115,7 @@ func newJsonClientCodec() *jsonClientCodec { } } +// Write codec data as byte. func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) { // If return error: it will be returned as is for this call. // Allow param to be only Array, Slice, Map or Struct. @@ -133,7 +135,7 @@ func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) { } case reflect.Array, reflect.Struct: case reflect.Ptr: - switch k := reflect.TypeOf(param).Elem().Kind(); k { + switch ptrK := reflect.TypeOf(param).Elem().Kind(); ptrK { case reflect.Map: if reflect.TypeOf(param).Elem().Key().Kind() == reflect.String { if reflect.ValueOf(param).Elem().IsNil() { @@ -146,7 +148,7 @@ func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) { } case reflect.Array, reflect.Struct: default: - return nil, perrors.New("unsupported param type: Ptr to " + k.String()) + return nil, perrors.New("unsupported param type: Ptr to " + ptrK.String()) } default: return nil, perrors.New("unsupported param type: " + k.String()) @@ -170,6 +172,7 @@ func (c *jsonClientCodec) Write(d *CodecData) ([]byte, error) { return buf.Bytes(), nil } +// Read bytes as structured data func (c *jsonClientCodec) Read(streamBytes []byte, x interface{}) error { c.rsp.reset() @@ -223,6 +226,7 @@ func (r *serverRequest) reset() { } } +// UnmarshalJSON unmarshals JSON for server request. func (r *serverRequest) UnmarshalJSON(raw []byte) error { r.reset() @@ -281,7 +285,7 @@ type serverResponse struct { Error interface{} `json:"error,omitempty"` } -// ServerCodec ... +// ServerCodec is codec data for request server. type ServerCodec struct { req serverRequest } @@ -296,7 +300,7 @@ func newServerCodec() *ServerCodec { return &ServerCodec{} } -// ReadHeader ... +// ReadHeader reads header and unmarshal to server codec func (c *ServerCodec) ReadHeader(header map[string]string, body []byte) error { if header["HttpMethod"] != "POST" { return &Error{Code: -32601, Message: "Method not found"} @@ -328,7 +332,7 @@ func (c *ServerCodec) ReadHeader(header map[string]string, body []byte) error { return nil } -// ReadBody ... +// ReadBody reads @x as request body. func (c *ServerCodec) ReadBody(x interface{}) error { // If x!=nil and return error e: // - Write() will be called with e.Error() in r.Error @@ -339,7 +343,7 @@ func (c *ServerCodec) ReadBody(x interface{}) error { return nil } - // 鍦ㄨ繖閲屾妸璇锋眰鍙傛暟json 瀛楃涓茶浆鎹㈡垚浜嗙浉搴旂殑struct + // the request parameter JSON string is converted to the corresponding struct params := []byte(*c.req.Params) if err := json.Unmarshal(*c.req.Params, x); err != nil { // Note: if c.request.Params is nil it's not an error, it's an optional member. @@ -362,7 +366,7 @@ func (c *ServerCodec) ReadBody(x interface{}) error { return nil } -// NewError ... +// NewError creates a error with @code and @message func NewError(code int, message string) *Error { return &Error{Code: code, Message: message} } @@ -380,6 +384,7 @@ func newError(message string) *Error { } } +// Write responses as byte func (c *ServerCodec) Write(errMsg string, x interface{}) ([]byte, error) { // If return error: nothing happens. // In r.Error will be "" or .Error() of error returned by: diff --git a/protocol/jsonrpc/jsonrpc_exporter.go b/protocol/jsonrpc/jsonrpc_exporter.go index 7f8fd491854f1ab25e63410a22ef5664db92f614..6f75f6aeae9fb1a8d75ded5f558e0ddae84686a0 100644 --- a/protocol/jsonrpc/jsonrpc_exporter.go +++ b/protocol/jsonrpc/jsonrpc_exporter.go @@ -28,23 +28,24 @@ import ( "github.com/apache/dubbo-go/protocol" ) -// JsonrpcExporter ... +// JsonrpcExporter is JSON RPC exporter and extends from base invoker. type JsonrpcExporter struct { protocol.BaseExporter } -// NewJsonrpcExporter ... +// NewJsonrpcExporter creates JSON RPC exporter with @key, @invoker and @exporterMap func NewJsonrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map) *JsonrpcExporter { return &JsonrpcExporter{ BaseExporter: *protocol.NewBaseExporter(key, invoker, exporterMap), } } -// Unexport ... +// Unexport exported JSON RPC service. func (je *JsonrpcExporter) Unexport() { serviceId := je.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") + interfaceName := je.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "") je.BaseExporter.Unexport() - err := common.ServiceMap.UnRegister(JSONRPC, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, JSONRPC, serviceId) if err != nil { logger.Errorf("[JsonrpcExporter.Unexport] error: %v", err) } diff --git a/protocol/jsonrpc/jsonrpc_invoker.go b/protocol/jsonrpc/jsonrpc_invoker.go index b6e194ce0e93e84c164eccf8574e5eb20430f6e8..d84b980216ade6e569e68af31fc90e1ea16b3056 100644 --- a/protocol/jsonrpc/jsonrpc_invoker.go +++ b/protocol/jsonrpc/jsonrpc_invoker.go @@ -29,13 +29,13 @@ import ( invocation_impl "github.com/apache/dubbo-go/protocol/invocation" ) -// JsonrpcInvoker ... +// JsonrpcInvoker is JSON RPC invoker type JsonrpcInvoker struct { protocol.BaseInvoker client *HTTPClient } -// NewJsonrpcInvoker ... +// NewJsonrpcInvoker creates JSON RPC invoker with @url and @client func NewJsonrpcInvoker(url common.URL, client *HTTPClient) *JsonrpcInvoker { return &JsonrpcInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), @@ -43,7 +43,7 @@ func NewJsonrpcInvoker(url common.URL, client *HTTPClient) *JsonrpcInvoker { } } -// Invoke ... +// Invoke the JSON RPC invocation and return result. func (ji *JsonrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { var ( diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go index 9e08eed2b4c61e686073a9039a605c4f73aa08c5..0f14ba11e2dec18bbd4d63e87e8c0fb2727f3755 100644 --- a/protocol/jsonrpc/jsonrpc_invoker_test.go +++ b/protocol/jsonrpc/jsonrpc_invoker_test.go @@ -36,7 +36,7 @@ import ( func TestJsonrpcInvoker_Invoke(t *testing.T) { - methods, err := common.ServiceMap.Register("jsonrpc", &UserProvider{}) + methods, err := common.ServiceMap.Register("UserProvider", "jsonrpc", &UserProvider{}) assert.NoError(t, err) assert.Equal(t, "GetUser,GetUser0,GetUser1,GetUser2,GetUser3,GetUser4", methods) diff --git a/protocol/jsonrpc/jsonrpc_protocol.go b/protocol/jsonrpc/jsonrpc_protocol.go index bed7099ab60a6c05c3799f993c0bb348a4b00f02..90a6bf5ef7aa017488f723804b22cc613850bcf2 100644 --- a/protocol/jsonrpc/jsonrpc_protocol.go +++ b/protocol/jsonrpc/jsonrpc_protocol.go @@ -44,14 +44,14 @@ func init() { var jsonrpcProtocol *JsonrpcProtocol -// JsonrpcProtocol ... +// JsonrpcProtocol is JSON RPC protocol. type JsonrpcProtocol struct { protocol.BaseProtocol serverMap map[string]*Server serverLock sync.Mutex } -// NewJsonrpcProtocol ... +// NewJsonrpcProtocol creates JSON RPC protocol func NewJsonrpcProtocol() *JsonrpcProtocol { return &JsonrpcProtocol{ BaseProtocol: protocol.NewBaseProtocol(), @@ -59,7 +59,7 @@ func NewJsonrpcProtocol() *JsonrpcProtocol { } } -// Export ... +// Export JSON RPC service for remote invocation func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := strings.TrimPrefix(url.Path, "/") @@ -74,7 +74,7 @@ func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { return exporter } -// Refer ... +// Refer a remote JSON PRC service from registry func (jp *JsonrpcProtocol) Refer(url common.URL) protocol.Invoker { //default requestTimeout var requestTimeout = config.GetConsumerConfig().RequestTimeout @@ -93,7 +93,7 @@ func (jp *JsonrpcProtocol) Refer(url common.URL) protocol.Invoker { return invoker } -// Destroy ... +// Destroy will destroy all invoker and exporter, so it only is called once. func (jp *JsonrpcProtocol) Destroy() { logger.Infof("jsonrpcProtocol destroy.") @@ -109,8 +109,8 @@ func (jp *JsonrpcProtocol) Destroy() { func (jp *JsonrpcProtocol) openServer(url common.URL) { _, ok := jp.serverMap[url.Location] if !ok { - _, ok := jp.ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) - if !ok { + _, loadOk := jp.ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) + if !loadOk { panic("[JsonrpcProtocol]" + url.Key() + "is not existing") } @@ -125,7 +125,7 @@ func (jp *JsonrpcProtocol) openServer(url common.URL) { } } -// GetProtocol ... +// GetProtocol gets JSON RPC protocol. func GetProtocol() protocol.Protocol { if jsonrpcProtocol == nil { jsonrpcProtocol = NewJsonrpcProtocol() diff --git a/protocol/jsonrpc/server.go b/protocol/jsonrpc/server.go index 8600f02dad3d32d797613823de0bbe40261d2e71..aa458a1614df29997b05ac4462200f9e9ffffc25 100644 --- a/protocol/jsonrpc/server.go +++ b/protocol/jsonrpc/server.go @@ -59,7 +59,7 @@ const ( PathPrefix = byte('/') ) -// Server ... +// Server is JSON RPC server wrapper type Server struct { done chan struct{} once sync.Once @@ -69,7 +69,7 @@ type Server struct { timeout time.Duration } -// NewServer ... +// NewServer creates new JSON RPC server. func NewServer() *Server { return &Server{ done: make(chan struct{}), @@ -228,7 +228,7 @@ func accept(listener net.Listener, fn func(net.Conn)) error { } } -// Start ... +// Start JSON RPC server then ready for accept request. func (s *Server) Start(url common.URL) { listener, err := net.Listen("tcp", url.Location) if err != nil { @@ -255,7 +255,7 @@ func (s *Server) Start(url common.URL) { }() } -// Stop ... +// Stop JSON RPC server, just can be call once. func (s *Server) Stop() { s.once.Do(func() { close(s.done) @@ -349,9 +349,9 @@ func serveRequest(ctx context.Context, constant.PATH_KEY: path, constant.VERSION_KEY: codec.req.Version})) if err := result.Error(); err != nil { - rspStream, err := codec.Write(err.Error(), invalidRequest) - if err != nil { - return perrors.WithStack(err) + rspStream, codecErr := codec.Write(err.Error(), invalidRequest) + if codecErr != nil { + return perrors.WithStack(codecErr) } if errRsp := sendErrorResp(header, rspStream); errRsp != nil { logger.Warnf("Exporter: sendErrorResp(header:%#v, error:%v) = error:%s", diff --git a/protocol/mock/mock_invoker.go b/protocol/mock/mock_invoker.go index 5c5b476b7b07f6c41a74a7ec8f51648aff84b1a3..0c88b47e36122dc1a9bc9345d7e93f62cd76f13b 100644 --- a/protocol/mock/mock_invoker.go +++ b/protocol/mock/mock_invoker.go @@ -1,18 +1,19 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ // Code generated by MockGen. DO NOT EDIT. // Source: invoker.go diff --git a/protocol/protocol.go b/protocol/protocol.go index a873469a8ba361c9dfc922b071ffbf256c6a8b98..6bed5ec4c2be82edda7a642eb342381c53387460 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -29,15 +29,20 @@ import ( // Protocol // Extension - protocol type Protocol interface { + // Export service for remote invocation Export(invoker Invoker) Exporter + // Refer a remote service Refer(url common.URL) Invoker + // Destroy will destroy all invoker and exporter, so it only is called once. Destroy() } // Exporter // wrapping invoker type Exporter interface { + // GetInvoker gets invoker. GetInvoker() Invoker + // Unexport exported service. Unexport() } @@ -45,45 +50,45 @@ type Exporter interface { // base protocol ///////////////////////////// -// BaseProtocol ... +// BaseProtocol is default protocol implement. type BaseProtocol struct { exporterMap *sync.Map invokers []Invoker } -// NewBaseProtocol ... +// NewBaseProtocol creates a new BaseProtocol func NewBaseProtocol() BaseProtocol { return BaseProtocol{ exporterMap: new(sync.Map), } } -// SetExporterMap ... +// SetExporterMap set @exporter with @key to local memory. func (bp *BaseProtocol) SetExporterMap(key string, exporter Exporter) { bp.exporterMap.Store(key, exporter) } -// ExporterMap ... +// ExporterMap gets exporter map. func (bp *BaseProtocol) ExporterMap() *sync.Map { return bp.exporterMap } -// SetInvokers ... +// SetInvokers sets invoker into local memory func (bp *BaseProtocol) SetInvokers(invoker Invoker) { bp.invokers = append(bp.invokers, invoker) } -// Invokers ... +// Invokers gets all invokers func (bp *BaseProtocol) Invokers() []Invoker { return bp.invokers } -// Export ... +// Export is default export implement. func (bp *BaseProtocol) Export(invoker Invoker) Exporter { return NewBaseExporter("base", invoker, bp.exporterMap) } -// Refer ... +// Refer is default refer implement. func (bp *BaseProtocol) Refer(url common.URL) Invoker { return NewBaseInvoker(url) } @@ -113,14 +118,14 @@ func (bp *BaseProtocol) Destroy() { // base exporter ///////////////////////////// -// BaseExporter ... +// BaseExporter is default exporter implement. type BaseExporter struct { key string invoker Invoker exporterMap *sync.Map } -// NewBaseExporter ... +// NewBaseExporter creates a new BaseExporter func NewBaseExporter(key string, invoker Invoker, exporterMap *sync.Map) *BaseExporter { return &BaseExporter{ key: key, @@ -129,13 +134,13 @@ func NewBaseExporter(key string, invoker Invoker, exporterMap *sync.Map) *BaseEx } } -// GetInvoker ... +// GetInvoker gets invoker func (de *BaseExporter) GetInvoker() Invoker { return de.invoker } -// Unexport ... +// Unexport exported service. func (de *BaseExporter) Unexport() { logger.Infof("Exporter unexport.") de.invoker.Destroy() diff --git a/protocol/protocolwrapper/mock_protocol_filter.go b/protocol/protocolwrapper/mock_protocol_filter.go index dedf8aa64b6ae1b7b4782350e2625b02171aac44..8763c20410915d4e81afd35691be7d9ed490f7a1 100644 --- a/protocol/protocolwrapper/mock_protocol_filter.go +++ b/protocol/protocolwrapper/mock_protocol_filter.go @@ -28,19 +28,22 @@ import ( type mockProtocolFilter struct{} -// NewMockProtocolFilter ... +// NewMockProtocolFilter creates a new mock protocol func NewMockProtocolFilter() protocol.Protocol { return &mockProtocolFilter{} } +// Export mock service for remote invocation func (pfw *mockProtocolFilter) Export(invoker protocol.Invoker) protocol.Exporter { return protocol.NewBaseExporter("key", invoker, &sync.Map{}) } +// Refer a mock remote service func (pfw *mockProtocolFilter) Refer(url common.URL) protocol.Invoker { return protocol.NewBaseInvoker(url) } +// Destroy will do nothing func (pfw *mockProtocolFilter) Destroy() { } diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index 70d2da0faed3bc9797eb23cec653bea05d445d91..cba1d5d5bce8c3de387381d17cc3f7965bf3adac 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -31,7 +31,7 @@ import ( ) const ( - // FILTER ... + // FILTER is protocol key. FILTER = "filter" ) @@ -45,7 +45,7 @@ type ProtocolFilterWrapper struct { protocol protocol.Protocol } -// Export ... +// Export service for remote invocation func (pfw *ProtocolFilterWrapper) Export(invoker protocol.Invoker) protocol.Exporter { if pfw.protocol == nil { pfw.protocol = extension.GetProtocol(invoker.GetUrl().Protocol) @@ -54,7 +54,7 @@ func (pfw *ProtocolFilterWrapper) Export(invoker protocol.Invoker) protocol.Expo return pfw.protocol.Export(invoker) } -// Refer ... +// Refer a remote service func (pfw *ProtocolFilterWrapper) Refer(url common.URL) protocol.Invoker { if pfw.protocol == nil { pfw.protocol = extension.GetProtocol(url.Protocol) @@ -62,7 +62,7 @@ func (pfw *ProtocolFilterWrapper) Refer(url common.URL) protocol.Invoker { return buildInvokerChain(pfw.protocol.Refer(url), constant.REFERENCE_FILTER_KEY) } -// Destroy ... +// Destroy will destroy all invoker and exporter. func (pfw *ProtocolFilterWrapper) Destroy() { pfw.protocol.Destroy() } diff --git a/protocol/rest/client/client_impl/resty_client.go b/protocol/rest/client/client_impl/resty_client.go index aa6c23137dc68492948b85a85555a5340572ac49..b60f50a5a70cde01a051dbb2a4490cbb792e0116 100644 --- a/protocol/rest/client/client_impl/resty_client.go +++ b/protocol/rest/client/client_impl/resty_client.go @@ -40,10 +40,12 @@ func init() { extension.SetRestClient(constant.DEFAULT_REST_CLIENT, NewRestyClient) } +// RestyClient a rest client implement by Resty type RestyClient struct { client *resty.Client } +// NewRestyClient a constructor of RestyClient func NewRestyClient(restOption *client.RestOptions) client.RestClient { client := resty.New() client.SetTransport( @@ -65,21 +67,21 @@ func NewRestyClient(restOption *client.RestOptions) client.RestClient { } } -func (rc *RestyClient) Do(restRequest *client.RestRequest, res interface{}) error { - r, err := rc.client.R(). - SetHeader("Content-Type", restRequest.Consumes). - SetHeader("Accept", restRequest.Produces). +// Do send request by RestyClient +func (rc *RestyClient) Do(restRequest *client.RestClientRequest, res interface{}) error { + req := rc.client.R() + req.Header = restRequest.Header + resp, err := req. SetPathParams(restRequest.PathParams). SetQueryParams(restRequest.QueryParams). - SetHeaders(restRequest.Headers). SetBody(restRequest.Body). SetResult(res). Execute(restRequest.Method, "http://"+path.Join(restRequest.Location, restRequest.Path)) if err != nil { return perrors.WithStack(err) } - if r.IsError() { - return perrors.New(r.String()) + if resp.IsError() { + return perrors.New(resp.String()) } return nil } diff --git a/protocol/rest/client/rest_client.go b/protocol/rest/client/rest_client.go index 7d020abc81c2bd44473ed25ffec4b2b657e7bcc0..d63c5e0bd0c7e6392eb5b0efc50bb0a585f4192d 100644 --- a/protocol/rest/client/rest_client.go +++ b/protocol/rest/client/rest_client.go @@ -18,26 +18,28 @@ package client import ( + "net/http" "time" ) +// RestOptions type RestOptions struct { RequestTimeout time.Duration ConnectTimeout time.Duration } -type RestRequest struct { +// RestClientRequest +type RestClientRequest struct { + Header http.Header Location string Path string - Produces string - Consumes string Method string PathParams map[string]string QueryParams map[string]string Body interface{} - Headers map[string]string } +// RestClient user can implement this client interface to send request type RestClient interface { - Do(request *RestRequest, res interface{}) error + Do(request *RestClientRequest, res interface{}) error } diff --git a/protocol/rest/rest_exporter.go b/protocol/rest/rest_exporter.go index 470d525ad806687e7a732ce5681eb372eb431a63..1ee208615ea07e3f2850920492ab9e9821e7ffef 100644 --- a/protocol/rest/rest_exporter.go +++ b/protocol/rest/rest_exporter.go @@ -40,8 +40,9 @@ func NewRestExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map func (re *RestExporter) Unexport() { serviceId := re.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") + interfaceName := re.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "") re.BaseExporter.Unexport() - err := common.ServiceMap.UnRegister(REST, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, REST, serviceId) if err != nil { logger.Errorf("[RestExporter.Unexport] error: %v", err) } diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go index 0c82035ac5eb9a52ab188baa971dbdf1b864e970..121d1217efd3baea0f961b67e243e9a0450aefc2 100644 --- a/protocol/rest/rest_invoker.go +++ b/protocol/rest/rest_invoker.go @@ -20,6 +20,7 @@ package rest import ( "context" "fmt" + "net/http" ) import ( @@ -56,7 +57,7 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio body interface{} pathParams map[string]string queryParams map[string]string - headers map[string]string + header http.Header err error ) if methodConfig == nil { @@ -71,24 +72,21 @@ func (ri *RestInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio result.Err = err return &result } - if headers, err = restStringMapTransform(methodConfig.HeadersMap, inv.Arguments()); err != nil { + if header, err = getRestHttpHeader(methodConfig, inv.Arguments()); err != nil { result.Err = err return &result } if len(inv.Arguments()) > methodConfig.Body && methodConfig.Body >= 0 { body = inv.Arguments()[methodConfig.Body] } - - req := &client.RestRequest{ + req := &client.RestClientRequest{ Location: ri.GetUrl().Location, - Produces: methodConfig.Produces, - Consumes: methodConfig.Consumes, Method: methodConfig.MethodType, Path: methodConfig.Path, PathParams: pathParams, QueryParams: queryParams, Body: body, - Headers: headers, + Header: header, } result.Err = ri.client.Do(req, inv.Reply()) if result.Err == nil { @@ -107,3 +105,17 @@ func restStringMapTransform(paramsMap map[int]string, args []interface{}) (map[s } return resMap, nil } + +func getRestHttpHeader(methodConfig *config.RestMethodConfig, args []interface{}) (http.Header, error) { + header := http.Header{} + headersMap := methodConfig.HeadersMap + header.Set("Content-Type", methodConfig.Consumes) + header.Set("Accept", methodConfig.Produces) + for k, v := range headersMap { + if k >= len(args) || k < 0 { + return nil, perrors.Errorf("[Rest Invoke] Index %v is out of bundle", k) + } + header.Set(v, fmt.Sprint(args[k])) + } + return header, nil +} diff --git a/protocol/rest/rest_invoker_test.go b/protocol/rest/rest_invoker_test.go index e44c5d9a21026992178bd432676c99bc837c361b..2ea260c58d03c27a691e48b953ce6d64f75040a2 100644 --- a/protocol/rest/rest_invoker_test.go +++ b/protocol/rest/rest_invoker_test.go @@ -61,7 +61,7 @@ func TestRestInvoker_Invoke(t *testing.T) { "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) - _, err = common.ServiceMap.Register(url.Protocol, &UserProvider{}) + _, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{}) assert.NoError(t, err) con := config.ProviderConfig{} config.SetProviderConfig(con) @@ -206,6 +206,6 @@ func TestRestInvoker_Invoke(t *testing.T) { assert.Error(t, res.Error(), "test error") assert.Equal(t, filterNum, 12) - err = common.ServiceMap.UnRegister(url.Protocol, "com.ikurento.user.UserProvider") + err = common.ServiceMap.UnRegister("UserProvider", url.Protocol, "com.ikurento.user.UserProvider") assert.NoError(t, err) } diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go index 47ecb6093b4cfa12a1d3397fa45d59b1e173a93a..e15eeb39d72212eb9f1d0235313eba231d3b0a36 100644 --- a/protocol/rest/rest_protocol.go +++ b/protocol/rest/rest_protocol.go @@ -75,7 +75,9 @@ func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { } rp.SetExporterMap(serviceKey, exporter) restServer := rp.getServer(url, restServiceConfig.Server) - restServer.Deploy(invoker, restServiceConfig.RestMethodConfigsMap) + for _, methodConfig := range restServiceConfig.RestMethodConfigsMap { + restServer.Deploy(methodConfig, server.GetRouteFunc(invoker, methodConfig)) + } return exporter } diff --git a/protocol/rest/rest_protocol_test.go b/protocol/rest/rest_protocol_test.go index 8af73a1839c159fdf58c64d12e039c20bb3221c6..9117148777ca868cb7d2672236e800c836d3de84 100644 --- a/protocol/rest/rest_protocol_test.go +++ b/protocol/rest/rest_protocol_test.go @@ -80,7 +80,7 @@ func TestRestProtocol_Export(t *testing.T) { "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + "side=provider&timeout=3000×tamp=1556509797245") assert.NoError(t, err) - _, err = common.ServiceMap.Register(url.Protocol, &UserProvider{}) + _, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{}) assert.NoError(t, err) con := config.ProviderConfig{} config.SetProviderConfig(con) @@ -128,7 +128,7 @@ func TestRestProtocol_Export(t *testing.T) { proto.Destroy() _, ok = proto.(*RestProtocol).serverMap[url.Location] assert.False(t, ok) - err = common.ServiceMap.UnRegister(url.Protocol, "com.ikurento.user.UserProvider") + err = common.ServiceMap.UnRegister("UserProvider", url.Protocol, "com.ikurento.user.UserProvider") assert.NoError(t, err) } diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index c10c98a7b677d47c43b64643a69d5b3768a6c663..fbd6fb7ad9dd81f043e4d45ee94a54e12ef89cdd 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -17,15 +17,306 @@ package server +import ( + "context" + "errors" + "net/http" + "reflect" + "strconv" + "strings" +) + +import ( + perrors "github.com/pkg/errors" +) + import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/rest/config" + "github.com/apache/dubbo-go/protocol/invocation" + rest_config "github.com/apache/dubbo-go/protocol/rest/config" ) +const parseParameterErrorStr = "An error occurred while parsing parameters on the server" + +// RestServer user can implement this server interface type RestServer interface { + // Start rest server Start(url common.URL) - Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) - UnDeploy(restMethodConfig map[string]*config.RestMethodConfig) + // Deploy a http api + Deploy(restMethodConfig *rest_config.RestMethodConfig, routeFunc func(request RestServerRequest, response RestServerResponse)) + // UnDeploy a http api + UnDeploy(restMethodConfig *rest_config.RestMethodConfig) + // Destroy rest server Destroy() } + +// RestServerRequest interface +type RestServerRequest interface { + // RawRequest get the Ptr of http.Request + RawRequest() *http.Request + // PathParameter get the path parameter by name + PathParameter(name string) string + // PathParameters get the map of the path parameters + PathParameters() map[string]string + // QueryParameter get the query parameter by name + QueryParameter(name string) string + // QueryParameters get the map of query parameters + QueryParameters(name string) []string + // BodyParameter get the body parameter of name + BodyParameter(name string) (string, error) + // HeaderParameter get the header parameter of name + HeaderParameter(name string) string + // ReadEntity checks the Accept header and reads the content into the entityPointer. + ReadEntity(entityPointer interface{}) error +} + +// RestServerResponse interface +type RestServerResponse interface { + http.ResponseWriter + // WriteError writes the http status and the error string on the response. err can be nil. + // Return an error if writing was not successful. + WriteError(httpStatus int, err error) (writeErr error) + // WriteEntity marshals the value using the representation denoted by the Accept Header. + WriteEntity(value interface{}) error +} + +// GetRouteFunc +// A route function will be invoked by http server +func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethodConfig) func(req RestServerRequest, resp RestServerResponse) { + return func(req RestServerRequest, resp RestServerResponse) { + var ( + err error + args []interface{} + ) + svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) + // get method + method := svc.Method()[methodConfig.MethodName] + argsTypes := method.ArgsType() + replyType := method.ReplyType() + // two ways to prepare arguments + // if method like this 'func1(req []interface{}, rsp *User) error' + // we don't have arguments type + if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && + argsTypes[0].String() == "[]interface {}" { + args, err = getArgsInterfaceFromRequest(req, methodConfig) + } else { + args, err = getArgsFromRequest(req, argsTypes, methodConfig) + } + if err != nil { + logger.Errorf("[Go Restful] parsing http parameters error:%v", err) + err = resp.WriteError(http.StatusInternalServerError, errors.New(parseParameterErrorStr)) + if err != nil { + logger.Errorf("[Go Restful] WriteErrorString error:%v", err) + } + } + result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]string))) + if result.Error() != nil { + err = resp.WriteError(http.StatusInternalServerError, result.Error()) + if err != nil { + logger.Errorf("[Go Restful] WriteError error:%v", err) + } + return + } + err = resp.WriteEntity(result.Result()) + if err != nil { + logger.Errorf("[Go Restful] WriteEntity error:%v", err) + } + } +} + +// getArgsInterfaceFromRequest when service function like GetUser(req []interface{}, rsp *User) error +// use this method to get arguments +func getArgsInterfaceFromRequest(req RestServerRequest, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) { + argsMap := make(map[int]interface{}, 8) + maxKey := 0 + for k, v := range methodConfig.PathParamsMap { + if maxKey < k { + maxKey = k + } + argsMap[k] = req.PathParameter(v) + } + for k, v := range methodConfig.QueryParamsMap { + if maxKey < k { + maxKey = k + } + params := req.QueryParameters(v) + if len(params) == 1 { + argsMap[k] = params[0] + } else { + argsMap[k] = params + } + } + for k, v := range methodConfig.HeadersMap { + if maxKey < k { + maxKey = k + } + argsMap[k] = req.HeaderParameter(v) + } + if methodConfig.Body >= 0 { + if maxKey < methodConfig.Body { + maxKey = methodConfig.Body + } + m := make(map[string]interface{}) + // TODO read as a slice + if err := req.ReadEntity(&m); err != nil { + return nil, perrors.Errorf("[Go restful] Read body entity as map[string]interface{} error:%v", err) + } + argsMap[methodConfig.Body] = m + } + args := make([]interface{}, maxKey+1) + for k, v := range argsMap { + if k >= 0 { + args[k] = v + } + } + return args, nil +} + +// getArgsFromRequest get arguments from server.RestServerRequest +func getArgsFromRequest(req RestServerRequest, argsTypes []reflect.Type, methodConfig *rest_config.RestMethodConfig) ([]interface{}, error) { + argsLength := len(argsTypes) + args := make([]interface{}, argsLength) + for i, t := range argsTypes { + args[i] = reflect.Zero(t).Interface() + } + if err := assembleArgsFromPathParams(methodConfig, argsLength, argsTypes, req, args); err != nil { + return nil, err + } + if err := assembleArgsFromQueryParams(methodConfig, argsLength, argsTypes, req, args); err != nil { + return nil, err + } + if err := assembleArgsFromBody(methodConfig, argsTypes, req, args); err != nil { + return nil, err + } + if err := assembleArgsFromHeaders(methodConfig, req, argsLength, argsTypes, args); err != nil { + return nil, err + } + return args, nil +} + +// assembleArgsFromHeaders assemble arguments from headers +func assembleArgsFromHeaders(methodConfig *rest_config.RestMethodConfig, req RestServerRequest, argsLength int, argsTypes []reflect.Type, args []interface{}) error { + for k, v := range methodConfig.HeadersMap { + param := req.HeaderParameter(v) + if k < 0 || k >= argsLength { + return perrors.Errorf("[Go restful] Header param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName) + } + t := argsTypes[k] + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + if t.Kind() == reflect.String { + args[k] = param + } else { + return perrors.Errorf("[Go restful] Header param parse error, the index %v args's type isn't string", k) + } + } + return nil +} + +// assembleArgsFromBody assemble arguments from body +func assembleArgsFromBody(methodConfig *rest_config.RestMethodConfig, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error { + if methodConfig.Body >= 0 && methodConfig.Body < len(argsTypes) { + t := argsTypes[methodConfig.Body] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + var ni interface{} + if t.String() == "[]interface {}" { + ni = make([]map[string]interface{}, 0) + } else if t.String() == "interface {}" { + ni = make(map[string]interface{}) + } else { + n := reflect.New(t) + if n.CanInterface() { + ni = n.Interface() + } + } + if err := req.ReadEntity(&ni); err != nil { + return perrors.Errorf("[Go restful] Read body entity error, error is %v", perrors.WithStack(err)) + } + args[methodConfig.Body] = ni + } + return nil +} + +// assembleArgsFromQueryParams assemble arguments from query params +func assembleArgsFromQueryParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error { + var ( + err error + param interface{} + i64 int64 + ) + for k, v := range methodConfig.QueryParamsMap { + if k < 0 || k >= argsLength { + return perrors.Errorf("[Go restful] Query param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName) + } + t := argsTypes[k] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + if kind == reflect.Slice { + param = req.QueryParameters(v) + } else if kind == reflect.String { + param = req.QueryParameter(v) + } else if kind == reflect.Int { + param, err = strconv.Atoi(req.QueryParameter(v)) + } else if kind == reflect.Int32 { + i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32) + if err == nil { + param = int32(i64) + } + } else if kind == reflect.Int64 { + param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64) + } else { + return perrors.Errorf("[Go restful] Query param parse error, the index %v args's type isn't int or string or slice", k) + } + if err != nil { + return perrors.Errorf("[Go restful] Query param parse error, error:%v", perrors.WithStack(err)) + } + args[k] = param + } + return nil +} + +// assembleArgsFromPathParams assemble arguments from path params +func assembleArgsFromPathParams(methodConfig *rest_config.RestMethodConfig, argsLength int, argsTypes []reflect.Type, req RestServerRequest, args []interface{}) error { + var ( + err error + param interface{} + i64 int64 + ) + for k, v := range methodConfig.PathParamsMap { + if k < 0 || k >= argsLength { + return perrors.Errorf("[Go restful] Path param parse error, the index %v args of method:%v doesn't exist", k, methodConfig.MethodName) + } + t := argsTypes[k] + kind := t.Kind() + if kind == reflect.Ptr { + t = t.Elem() + } + if kind == reflect.Int { + param, err = strconv.Atoi(req.PathParameter(v)) + } else if kind == reflect.Int32 { + i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32) + if err == nil { + param = int32(i64) + } + } else if kind == reflect.Int64 { + param, err = strconv.ParseInt(req.PathParameter(v), 10, 64) + } else if kind == reflect.String { + param = req.PathParameter(v) + } else { + return perrors.Errorf("[Go restful] Path param parse error, the index %v args's type isn't int or string", k) + } + if err != nil { + return perrors.Errorf("[Go restful] Path param parse error, error is %v", perrors.WithStack(err)) + } + args[k] = param + } + return nil +} diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 69f36a5c80aa51f52dfcfabc5a1bd4003f4cd727..c7d971fcaa5ada0ba02cc436b5ae6705793887ef 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -22,8 +22,6 @@ import ( "fmt" "net" "net/http" - "reflect" - "strconv" "strings" "time" ) @@ -38,27 +36,29 @@ 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/protocol" - "github.com/apache/dubbo-go/protocol/invocation" "github.com/apache/dubbo-go/protocol/rest/config" "github.com/apache/dubbo-go/protocol/rest/server" ) func init() { - extension.SetRestServer(constant.DEFAULT_REST_SERVER, GetNewGoRestfulServer) + extension.SetRestServer(constant.DEFAULT_REST_SERVER, NewGoRestfulServer) } var filterSlice []restful.FilterFunction +// GoRestfulServer a rest server implement by go-restful type GoRestfulServer struct { srv *http.Server container *restful.Container } -func NewGoRestfulServer() *GoRestfulServer { +// NewGoRestfulServer a constructor of GoRestfulServer +func NewGoRestfulServer() server.RestServer { return &GoRestfulServer{} } +// Start go-restful server +// It will add all go-restful filters func (grs *GoRestfulServer) Start(url common.URL) { grs.container = restful.NewContainer() for _, filter := range filterSlice { @@ -80,61 +80,32 @@ func (grs *GoRestfulServer) Start(url common.URL) { }() } -func (grs *GoRestfulServer) Deploy(invoker protocol.Invoker, restMethodConfig map[string]*config.RestMethodConfig) { - svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) - for methodName, config := range restMethodConfig { - // get method - method := svc.Method()[methodName] - argsTypes := method.ArgsType() - replyType := method.ReplyType() - ws := new(restful.WebService) - ws.Path(config.Path). - Produces(strings.Split(config.Produces, ",")...). - Consumes(strings.Split(config.Consumes, ",")...). - Route(ws.Method(config.MethodType).To(getFunc(methodName, invoker, argsTypes, replyType, config))) - grs.container.Add(ws) +// Publish a http api in go-restful server +// The routeFunc should be invoked when the server receive a request +func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, routeFunc func(request server.RestServerRequest, response server.RestServerResponse)) { + ws := &restful.WebService{} + rf := func(req *restful.Request, resp *restful.Response) { + routeFunc(NewGoRestfulRequestAdapter(req), resp) } + ws.Path(restMethodConfig.Path). + Produces(strings.Split(restMethodConfig.Produces, ",")...). + Consumes(strings.Split(restMethodConfig.Consumes, ",")...). + Route(ws.Method(restMethodConfig.MethodType).To(rf)) + grs.container.Add(ws) } -func getFunc(methodName string, invoker protocol.Invoker, argsTypes []reflect.Type, - replyType reflect.Type, config *config.RestMethodConfig) func(req *restful.Request, resp *restful.Response) { - return func(req *restful.Request, resp *restful.Response) { - var ( - err error - args []interface{} - ) - if (len(argsTypes) == 1 || len(argsTypes) == 2 && replyType == nil) && - argsTypes[0].String() == "[]interface {}" { - args = getArgsInterfaceFromRequest(req, config) - } else { - args = getArgsFromRequest(req, argsTypes, config) - } - result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodName, args, make(map[string]string))) - if result.Error() != nil { - err = resp.WriteError(http.StatusInternalServerError, result.Error()) - if err != nil { - logger.Errorf("[Go Restful] WriteError error:%v", err) - } - return - } - err = resp.WriteEntity(result.Result()) - if err != nil { - logger.Error("[Go Restful] WriteEntity error:%v", err) - } - } -} -func (grs *GoRestfulServer) UnDeploy(restMethodConfig map[string]*config.RestMethodConfig) { - for _, config := range restMethodConfig { - ws := new(restful.WebService) - ws.Path(config.Path) - err := grs.container.Remove(ws) - if err != nil { - logger.Warnf("[Go restful] Remove web service error:%v", err) - } +// Delete a http api in go-restful server +func (grs *GoRestfulServer) UnDeploy(restMethodConfig *config.RestMethodConfig) { + ws := new(restful.WebService) + ws.Path(restMethodConfig.Path) + err := grs.container.Remove(ws) + if err != nil { + logger.Warnf("[Go restful] Remove web service error:%v", err) } } +// Destroy the go-restful server func (grs *GoRestfulServer) Destroy() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -144,179 +115,59 @@ func (grs *GoRestfulServer) Destroy() { logger.Infof("[Go Restful] Server exiting") } -func getArgsInterfaceFromRequest(req *restful.Request, config *config.RestMethodConfig) []interface{} { - argsMap := make(map[int]interface{}, 8) - maxKey := 0 - for k, v := range config.PathParamsMap { - if maxKey < k { - maxKey = k - } - argsMap[k] = req.PathParameter(v) - } - for k, v := range config.QueryParamsMap { - if maxKey < k { - maxKey = k - } - params := req.QueryParameters(v) - if len(params) == 1 { - argsMap[k] = params[0] - } else { - argsMap[k] = params - } - } - for k, v := range config.HeadersMap { - if maxKey < k { - maxKey = k - } - argsMap[k] = req.HeaderParameter(v) - } - if config.Body >= 0 { - if maxKey < config.Body { - maxKey = config.Body - } - m := make(map[string]interface{}) - // TODO read as a slice - if err := req.ReadEntity(&m); err != nil { - logger.Warnf("[Go restful] Read body entity as map[string]interface{} error:%v", perrors.WithStack(err)) - } else { - argsMap[config.Body] = m - } - } - args := make([]interface{}, maxKey+1) - for k, v := range argsMap { - if k >= 0 { - args[k] = v - } - } - return args +// AddGoRestfulServerFilter let user add the http server of go-restful +// addFilter should before config.Load() +func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { + filterSlice = append(filterSlice, filterFuc) } -func getArgsFromRequest(req *restful.Request, argsTypes []reflect.Type, config *config.RestMethodConfig) []interface{} { - argsLength := len(argsTypes) - args := make([]interface{}, argsLength) - for i, t := range argsTypes { - args[i] = reflect.Zero(t).Interface() - } - var ( - err error - param interface{} - i64 int64 - ) - for k, v := range config.PathParamsMap { - if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Path param parse error, the args:%v doesn't exist", k) - continue - } - t := argsTypes[k] - kind := t.Kind() - if kind == reflect.Ptr { - t = t.Elem() - } - if kind == reflect.Int { - param, err = strconv.Atoi(req.PathParameter(v)) - } else if kind == reflect.Int32 { - i64, err = strconv.ParseInt(req.PathParameter(v), 10, 32) - if err == nil { - param = int32(i64) - } - } else if kind == reflect.Int64 { - param, err = strconv.ParseInt(req.PathParameter(v), 10, 64) - } else if kind == reflect.String { - param = req.PathParameter(v) - } else { - logger.Warnf("[Go restful] Path param parse error, the args:%v of type isn't int or string", k) - continue - } - if err != nil { - logger.Errorf("[Go restful] Path param parse error, error is %v", err) - continue - } - args[k] = param - } - for k, v := range config.QueryParamsMap { - if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Query param parse error, the args:%v doesn't exist", k) - continue - } - t := argsTypes[k] - kind := t.Kind() - if kind == reflect.Ptr { - t = t.Elem() - } - if kind == reflect.Slice { - param = req.QueryParameters(v) - } else if kind == reflect.String { - param = req.QueryParameter(v) - } else if kind == reflect.Int { - param, err = strconv.Atoi(req.QueryParameter(v)) - } else if kind == reflect.Int32 { - i64, err = strconv.ParseInt(req.QueryParameter(v), 10, 32) - if err == nil { - param = int32(i64) - } - } else if kind == reflect.Int64 { - param, err = strconv.ParseInt(req.QueryParameter(v), 10, 64) - } else { - logger.Errorf("[Go restful] Query param parse error, the args:%v of type isn't int or string or slice", k) - continue - } - if err != nil { - logger.Errorf("[Go restful] Query param parse error, error is %v", err) - continue - } - args[k] = param - } +// GoRestfulRequestAdapter a adapter struct about RestServerRequest +type GoRestfulRequestAdapter struct { + server.RestServerRequest + request *restful.Request +} - if config.Body >= 0 && config.Body < len(argsTypes) { - t := argsTypes[config.Body] - kind := t.Kind() - if kind == reflect.Ptr { - t = t.Elem() - } - var ni interface{} - if t.String() == "[]interface {}" { - ni = make([]map[string]interface{}, 0) - } else if t.String() == "interface {}" { - ni = make(map[string]interface{}) - } else { - n := reflect.New(t) - if n.CanInterface() { - ni = n.Interface() - } - } - if err := req.ReadEntity(&ni); err != nil { - logger.Errorf("[Go restful] Read body entity error:%v", err) - } else { - args[config.Body] = ni - } - } +// NewGoRestfulRequestAdapter a constructor of GoRestfulRequestAdapter +func NewGoRestfulRequestAdapter(request *restful.Request) *GoRestfulRequestAdapter { + return &GoRestfulRequestAdapter{request: request} +} - for k, v := range config.HeadersMap { - param := req.HeaderParameter(v) - if k < 0 || k >= argsLength { - logger.Errorf("[Go restful] Header param parse error, the args:%v doesn't exist", k) - continue - } - t := argsTypes[k] - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - if t.Kind() == reflect.String { - args[k] = param - } else { - logger.Errorf("[Go restful] Header param parse error, the args:%v of type isn't string", k) - } - } +// RawRequest a adapter function of server.RestServerRequest's RawRequest +func (grra *GoRestfulRequestAdapter) RawRequest() *http.Request { + return grra.request.Request +} - return args +// PathParameter a adapter function of server.RestServerRequest's PathParameter +func (grra *GoRestfulRequestAdapter) PathParameter(name string) string { + return grra.request.PathParameter(name) } -func GetNewGoRestfulServer() server.RestServer { - return NewGoRestfulServer() +// PathParameters a adapter function of server.RestServerRequest's QueryParameter +func (grra *GoRestfulRequestAdapter) PathParameters() map[string]string { + return grra.request.PathParameters() } -// Let user addFilter -// addFilter should before config.Load() -func AddGoRestfulServerFilter(filterFuc restful.FilterFunction) { - filterSlice = append(filterSlice, filterFuc) +// QueryParameter a adapter function of server.RestServerRequest's QueryParameters +func (grra *GoRestfulRequestAdapter) QueryParameter(name string) string { + return grra.request.QueryParameter(name) +} + +// QueryParameters a adapter function of server.RestServerRequest's QueryParameters +func (grra *GoRestfulRequestAdapter) QueryParameters(name string) []string { + return grra.request.QueryParameters(name) +} + +// BodyParameter a adapter function of server.RestServerRequest's BodyParameter +func (grra *GoRestfulRequestAdapter) BodyParameter(name string) (string, error) { + return grra.request.BodyParameter(name) +} + +// HeaderParameter a adapter function of server.RestServerRequest's HeaderParameter +func (grra *GoRestfulRequestAdapter) HeaderParameter(name string) string { + return grra.request.HeaderParameter(name) +} + +// ReadEntity a adapter func of server.RestServerRequest's ReadEntity +func (grra *GoRestfulRequestAdapter) ReadEntity(entityPointer interface{}) error { + return grra.request.ReadEntity(entityPointer) } diff --git a/protocol/result.go b/protocol/result.go index 34e76d2dddbaed33b2e2c015631443565cfaea87..2e7a6e492a60888ec9d9f420c77e6b77aee6aa70 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -17,15 +17,23 @@ package protocol -// Result ... +// Result is a RPC result type Result interface { + // SetError sets error. SetError(error) + // Error gets error. Error() error + // SetResult sets invoker result. SetResult(interface{}) + // Result gets invoker result. Result() interface{} + // SetAttachments replaces the existing attachments with the specified param. SetAttachments(map[string]string) + // Attachments gets all attachments Attachments() map[string]string + // AddAttachment adds the specified map to existing attachments in this instance. AddAttachment(string, string) + // Attachment gets attachment by key with default value. Attachment(string, string) string } @@ -33,48 +41,49 @@ type Result interface { // Result Impletment of RPC ///////////////////////////// -// RPCResult ... +// RPCResult is default RPC result. type RPCResult struct { Attrs map[string]string Err error Rest interface{} } -// SetError ... +// SetError sets error. func (r *RPCResult) SetError(err error) { r.Err = err } +// Error gets error. func (r *RPCResult) Error() error { return r.Err } -// SetResult ... +// SetResult sets invoker result. func (r *RPCResult) SetResult(rest interface{}) { r.Rest = rest } -// Result ... +// Result gets invoker result. func (r *RPCResult) Result() interface{} { return r.Rest } -// SetAttachments ... +// SetAttachments replaces the existing attachments with the specified param. func (r *RPCResult) SetAttachments(attr map[string]string) { r.Attrs = attr } -// Attachments ... +// Attachments gets all attachments func (r *RPCResult) Attachments() map[string]string { return r.Attrs } -// AddAttachment ... +// AddAttachment adds the specified map to existing attachments in this instance. func (r *RPCResult) AddAttachment(key, value string) { r.Attrs[key] = value } -// Attachment ... +// Attachment gets attachment by key with default value. func (r *RPCResult) Attachment(key, defaultValue string) string { v, ok := r.Attrs[key] if !ok { diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go index 13be47c98ece1cc006250ad49ab2b9a8c3b1f625..0eeca6c1bba1f471117eb687a92d0458b9d5901d 100644 --- a/protocol/rpc_status.go +++ b/protocol/rpc_status.go @@ -32,7 +32,7 @@ var ( serviceStatistic sync.Map // url -> RPCStatus ) -// RPCStatus ... +// RPCStatus is URL statistics. type RPCStatus struct { active int32 failed int32 @@ -46,63 +46,63 @@ type RPCStatus struct { lastRequestFailedTimestamp int64 } -// GetActive ... +// GetActive gets active. func (rpc *RPCStatus) GetActive() int32 { return atomic.LoadInt32(&rpc.active) } -// GetFailed ... +// GetFailed gets failed. func (rpc *RPCStatus) GetFailed() int32 { return atomic.LoadInt32(&rpc.failed) } -// GetTotal ... +// GetTotal gets total. func (rpc *RPCStatus) GetTotal() int32 { return atomic.LoadInt32(&rpc.total) } -// GetTotalElapsed ... +// GetTotalElapsed gets total elapsed. func (rpc *RPCStatus) GetTotalElapsed() int64 { return atomic.LoadInt64(&rpc.totalElapsed) } -// GetFailedElapsed ... +// GetFailedElapsed gets failed elapsed. func (rpc *RPCStatus) GetFailedElapsed() int64 { return atomic.LoadInt64(&rpc.failedElapsed) } -// GetMaxElapsed ... +// GetMaxElapsed gets max elapsed. func (rpc *RPCStatus) GetMaxElapsed() int64 { return atomic.LoadInt64(&rpc.maxElapsed) } -// GetFailedMaxElapsed ... +// GetFailedMaxElapsed gets failed max elapsed. func (rpc *RPCStatus) GetFailedMaxElapsed() int64 { return atomic.LoadInt64(&rpc.failedMaxElapsed) } -// GetSucceededMaxElapsed ... +// GetSucceededMaxElapsed gets succeeded max elapsed. func (rpc *RPCStatus) GetSucceededMaxElapsed() int64 { return atomic.LoadInt64(&rpc.succeededMaxElapsed) } -// GetLastRequestFailedTimestamp ... +// GetLastRequestFailedTimestamp gets last request failed timestamp. func (rpc *RPCStatus) GetLastRequestFailedTimestamp() int64 { return atomic.LoadInt64(&rpc.lastRequestFailedTimestamp) } -// GetSuccessiveRequestFailureCount ... +// GetSuccessiveRequestFailureCount gets successive request failure count. func (rpc *RPCStatus) GetSuccessiveRequestFailureCount() int32 { return atomic.LoadInt32(&rpc.successiveRequestFailureCount) } -// GetURLStatus ... +// GetURLStatus get URL RPC status. func GetURLStatus(url common.URL) *RPCStatus { rpcStatus, _ := serviceStatistic.LoadOrStore(url.Key(), &RPCStatus{}) return rpcStatus.(*RPCStatus) } -// GetMethodStatus ... +// GetMethodStatus get method RPC status. func GetMethodStatus(url common.URL, methodName string) *RPCStatus { identifier := url.Key() methodMap, found := methodStatistics.Load(identifier) @@ -122,13 +122,13 @@ func GetMethodStatus(url common.URL, methodName string) *RPCStatus { return status } -// BeginCount ... +// BeginCount gets begin count. func BeginCount(url common.URL, methodName string) { beginCount0(GetURLStatus(url)) beginCount0(GetMethodStatus(url, methodName)) } -// EndCount ... +// EndCount gets end count. func EndCount(url common.URL, methodName string, elapsed int64, succeeded bool) { endCount0(GetURLStatus(url), elapsed, succeeded) endCount0(GetMethodStatus(url, methodName), elapsed, succeeded) @@ -163,7 +163,7 @@ func endCount0(rpcStatus *RPCStatus, elapsed int64, succeeded bool) { } } -// CurrentTimeMillis ... +// CurrentTimeMillis get current timestamp func CurrentTimeMillis() int64 { return time.Now().UnixNano() / int64(time.Millisecond) } diff --git a/protocol/rpc_status_test.go b/protocol/rpc_status_test.go index 5a07f44eab0db60ba65a155d6c6190ab4ce2d716..611b7cba6e453cdf00624b4414d468278fd62cb1 100644 --- a/protocol/rpc_status_test.go +++ b/protocol/rpc_status_test.go @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package protocol import ( diff --git a/registry/base_registry.go b/registry/base_registry.go index 3b64e93e2f6b5b58a70650f589dec3ca092376c1..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 } @@ -121,6 +127,7 @@ func (r *BaseRegistry) Destroy() { close(r.done) // wait waitgroup done (wait listeners outside close over) r.wg.Wait() + //close registry client r.closeRegisters() } @@ -153,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()) @@ -178,13 +222,28 @@ func (r *BaseRegistry) RestartCallBack() bool { } logger.Infof("success to re-register service :%v", confIf.Key()) } - r.facadeBasedRegistry.InitListeners() + + if flag { + r.facadeBasedRegistry.InitListeners() + } return flag } // 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 @@ -209,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) @@ -225,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 @@ -236,28 +302,20 @@ 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) } - params.Add("anyhost", "true") + params.Add(constant.ANYHOST_KEY, "true") // Dubbo java consumer to start looking for the provider url,because the category does not match, // the provider will not find, causing the consumer can not start, so we use consumers. - // DubboRole = [...]string{"consumer", "", "", "provider"} - // params.Add("category", (RoleType(PROVIDER)).Role()) - params.Add("category", (common.RoleType(common.PROVIDER)).String()) - params.Add("dubbo", "dubbo-provider-golang-"+constant.Version) - - params.Add("side", (common.RoleType(common.PROVIDER)).Role()) if len(c.Methods) == 0 { - params.Add("methods", strings.Join(c.Methods, ",")) + params.Add(constant.METHODS_KEY, strings.Join(c.Methods, ",")) } logger.Debugf("provider url params:%#v", params) var host string @@ -276,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 @@ -284,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)) @@ -308,9 +361,6 @@ func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values) (string } params.Add("protocol", c.Protocol) - params.Add("category", (common.RoleType(common.CONSUMER)).String()) - params.Add("dubbo", "dubbogo-consumer-"+constant.Version) - rawURL = fmt.Sprintf("consumer://%s%s?%s", localIP, c.Path, params.Encode()) dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), (common.RoleType(common.CONSUMER)).String()) @@ -328,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) @@ -363,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/listener.go b/registry/consul/listener.go index b047a4c08c9f6c809ed3dca8bd0d06ceaa576cae..5fac9ec0f9b6c08620021de9d0b92e3b94773c12 100644 --- a/registry/consul/listener.go +++ b/registry/consul/listener.go @@ -142,7 +142,6 @@ func (l *consulListener) run() { func (l *consulListener) handler(idx uint64, raw interface{}) { var ( service *consul.ServiceEntry - event *registry.ServiceEvent url common.URL ok bool err error @@ -183,7 +182,7 @@ func (l *consulListener) handler(idx uint64, raw interface{}) { } l.urls = newUrls - for _, event = range events { + for _, event := range events { l.eventCh <- event } } diff --git a/registry/consul/registry.go b/registry/consul/registry.go index c5b8510a6c87068a5b4f1ce52203d401a896a6c2..bd394be44356c201b6bf7c88a61bfb2dafa0eb15 100644 --- a/registry/consul/registry.go +++ b/registry/consul/registry.go @@ -74,6 +74,8 @@ func newConsulRegistry(url *common.URL) (registry.Registry, error) { return r, nil } +// Register register @url +// it delegate the job to register() method func (r *consulRegistry) Register(url common.URL) error { var err error @@ -87,6 +89,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,7 +98,9 @@ func (r *consulRegistry) register(url common.URL) error { return r.client.Agent().ServiceRegister(service) } -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, "")) @@ -108,17 +113,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)) } -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/directory/directory.go b/registry/directory/directory.go index a6d2cdf49b0935b2402e03208d1ff5f702e1cc52..253dc597f9981c61574b8af97119653652d51bbc 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -19,7 +19,6 @@ package directory import ( "sync" - "time" ) import ( @@ -28,6 +27,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/cluster/directory" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" @@ -42,67 +42,57 @@ import ( "github.com/apache/dubbo-go/remoting" ) -// Options ... -type Options struct { - serviceTTL time.Duration +func init() { + extension.SetDefaultRegistryDirectory(NewRegistryDirectory) } -// Option ... -type Option func(*Options) - -type registryDirectory struct { +type RegistryDirectory struct { directory.BaseDirectory cacheInvokers []protocol.Invoker 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 referenceConfigurationListener *referenceConfigurationListener - Options - serviceKey string - forbidden atomic.Bool + serviceKey string + forbidden atomic.Bool } -// NewRegistryDirectory ... -func NewRegistryDirectory(url *common.URL, registry registry.Registry, opts ...Option) (*registryDirectory, error) { - options := Options{ - //default 300s - serviceTTL: time.Duration(300e9), - } - for _, opt := range opts { - opt(&options) - } +// NewRegistryDirectory will create a new RegistryDirectory +func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster.Directory, error) { if url.SubURL == nil { return nil, perrors.Errorf("url is invalid, suburl can not be nil") } - dir := ®istryDirectory{ + dir := &RegistryDirectory{ BaseDirectory: directory.NewBaseDirectory(url), cacheInvokers: []protocol.Invoker{}, cacheInvokersMap: &sync.Map{}, serviceType: url.SubURL.Service(), registry: registry, - Options: options, } dir.consumerConfigurationListener = newConsumerConfigurationListener(dir) + + go dir.subscribe(url.SubURL) return dir, nil } -//subscribe from registry -func (dir *registryDirectory) Subscribe(url *common.URL) { +// subscribe from registry +func (dir *RegistryDirectory) subscribe(url *common.URL) { dir.consumerConfigurationListener.addNotifyListener(dir) dir.referenceConfigurationListener = newReferenceConfigurationListener(dir, url) dir.registry.Subscribe(url, dir) } -func (dir *registryDirectory) Notify(event *registry.ServiceEvent) { +// Notify monitor changes from registry,and update the cacheServices +func (dir *RegistryDirectory) Notify(event *registry.ServiceEvent) { go dir.update(event) } -//subscribe service from registry, and update the cacheServices -func (dir *registryDirectory) update(res *registry.ServiceEvent) { +// update the cacheServices and subscribe service from registry +func (dir *RegistryDirectory) update(res *registry.ServiceEvent) { if res == nil { return } @@ -111,37 +101,36 @@ func (dir *registryDirectory) update(res *registry.ServiceEvent) { dir.refreshInvokers(res) } -func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) { +func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) { var ( 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 + } switch res.Action { case remoting.EventTypeAdd, remoting.EventTypeUpdate: logger.Infof("selector add service url{%s}", res.Service) - var urls []*common.URL - for _, v := range directory.GetRouterURLSet().Values() { + var urls []*common.URL + for _, v := range config.GetRouterURLSet().Values() { urls = append(urls, v.(*common.URL)) } if len(urls) > 0 { dir.SetRouters(urls) } - - //dir.cacheService.EventTypeAdd(res.Path, dir.serviceTTL) oldInvoker = dir.cacheInvoker(url) case remoting.EventTypeDel: oldInvoker = dir.uncacheInvoker(url) @@ -163,10 +152,12 @@ func (dir *registryDirectory) refreshInvokers(res *registry.ServiceEvent) { } -func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker { - newInvokersList := []protocol.Invoker{} +func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker { + var ( + err error + newInvokersList []protocol.Invoker + ) groupInvokersMap := make(map[string][]protocol.Invoker) - groupInvokersList := []protocol.Invoker{} dir.cacheInvokersMap.Range(func(key, value interface{}) bool { newInvokersList = append(newInvokersList, value.(protocol.Invoker)) @@ -182,25 +173,30 @@ func (dir *registryDirectory) toGroupInvokers() []protocol.Invoker { groupInvokersMap[group] = []protocol.Invoker{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 } } else { for _, invokers := range groupInvokersMap { staticDir := directory.NewStaticDirectory(invokers) - cluster := extension.GetCluster(dir.GetUrl().SubURL.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER)) - staticDir.BuildRouterChain(invokers) - groupInvokersList = append(groupInvokersList, cluster.Join(staticDir)) + cst := extension.GetCluster(dir.GetUrl().SubURL.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER)) + err = staticDir.BuildRouterChain(invokers) + if err != nil { + logger.Error(err) + continue + } + groupInvokersList = append(groupInvokersList, cst.Join(staticDir)) } } return groupInvokersList } -// uncacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil -func (dir *registryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker { +// uncacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil +func (dir *RegistryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker { logger.Debugf("service will be deleted in cache invokers: invokers key is %s!", url.Key()) if cacheInvoker, ok := dir.cacheInvokersMap.Load(url.Key()); ok { dir.cacheInvokersMap.Delete(url.Key()) @@ -209,8 +205,8 @@ func (dir *registryDirectory) uncacheInvoker(url *common.URL) protocol.Invoker { return nil } -// cacheInvoker return abandoned Invoker,if no Invoker to be abandoned,return nil -func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { +// cacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil +func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { dir.overrideUrl(dir.GetDirectoryUrl()) referenceUrl := dir.GetDirectoryUrl().SubURL @@ -223,7 +219,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) @@ -245,8 +241,8 @@ func (dir *registryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { return nil } -//select the protocol invokers from the directory -func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.Invoker { +// List selected protocol invokers from the directory +func (dir *RegistryDirectory) List(invocation protocol.Invocation) []protocol.Invoker { invokers := dir.cacheInvokers routerChain := dir.RouterChain() @@ -256,7 +252,8 @@ func (dir *registryDirectory) List(invocation protocol.Invocation) []protocol.In return routerChain.Route(invokers, dir.cacheOriginUrl, invocation) } -func (dir *registryDirectory) IsAvailable() bool { +// IsAvailable whether the directory is available +func (dir *RegistryDirectory) IsAvailable() bool { if !dir.BaseDirectory.IsAvailable() { return dir.BaseDirectory.IsAvailable() } @@ -270,8 +267,9 @@ func (dir *registryDirectory) IsAvailable() bool { return false } -func (dir *registryDirectory) Destroy() { - //TODO:unregister & unsubscribe +// Destroy method +func (dir *RegistryDirectory) Destroy() { + // TODO:unregister & unsubscribe dir.BaseDirectory.Destroy(func() { invokers := dir.cacheInvokers dir.cacheInvokers = []protocol.Invoker{} @@ -281,7 +279,7 @@ func (dir *registryDirectory) Destroy() { }) } -func (dir *registryDirectory) overrideUrl(targetUrl *common.URL) { +func (dir *RegistryDirectory) overrideUrl(targetUrl *common.URL) { doOverrideUrl(dir.configurators, targetUrl) doOverrideUrl(dir.consumerConfigurationListener.Configurators(), targetUrl) doOverrideUrl(dir.referenceConfigurationListener.Configurators(), targetUrl) @@ -295,11 +293,11 @@ func doOverrideUrl(configurators []config_center.Configurator, targetUrl *common type referenceConfigurationListener struct { registry.BaseConfigurationListener - directory *registryDirectory + directory *RegistryDirectory url *common.URL } -func newReferenceConfigurationListener(dir *registryDirectory, url *common.URL) *referenceConfigurationListener { +func newReferenceConfigurationListener(dir *RegistryDirectory, url *common.URL) *referenceConfigurationListener { listener := &referenceConfigurationListener{directory: dir, url: url} listener.InitWith( url.EncodedServiceKey()+constant.CONFIGURATORS_SUFFIX, @@ -309,6 +307,7 @@ func newReferenceConfigurationListener(dir *registryDirectory, url *common.URL) return listener } +// Process handle events and update Invokers func (l *referenceConfigurationListener) Process(event *config_center.ConfigChangeEvent) { l.BaseConfigurationListener.Process(event) l.directory.refreshInvokers(nil) @@ -317,10 +316,10 @@ func (l *referenceConfigurationListener) Process(event *config_center.ConfigChan type consumerConfigurationListener struct { registry.BaseConfigurationListener listeners []registry.NotifyListener - directory *registryDirectory + directory *RegistryDirectory } -func newConsumerConfigurationListener(dir *registryDirectory) *consumerConfigurationListener { +func newConsumerConfigurationListener(dir *RegistryDirectory) *consumerConfigurationListener { listener := &consumerConfigurationListener{directory: dir} listener.InitWith( config.GetConsumerConfig().ApplicationConfig.Name+constant.CONFIGURATORS_SUFFIX, @@ -334,6 +333,7 @@ func (l *consumerConfigurationListener) addNotifyListener(listener registry.Noti l.listeners = append(l.listeners, listener) } +// Process handles events from Configuration Center and update Invokers func (l *consumerConfigurationListener) Process(event *config_center.ConfigChangeEvent) { l.BaseConfigurationListener.Process(event) l.directory.refreshInvokers(nil) diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go index 0dde44e73c18f65f262e01f499e198995907dece..f2b2f8edd2d46950d2e74733b1d869e0de282ec0 100644 --- a/registry/directory/directory_test.go +++ b/registry/directory/directory_test.go @@ -25,6 +25,7 @@ import ( ) import ( + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -37,13 +38,18 @@ import ( "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/protocol/mock" "github.com/apache/dubbo-go/protocol/protocolwrapper" "github.com/apache/dubbo-go/registry" "github.com/apache/dubbo-go/remoting" ) 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) { @@ -58,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) //} @@ -79,10 +85,9 @@ func TestSubscribe_Group(t *testing.T) { suburl.SetParam(constant.CLUSTER_KEY, "mock") regurl.SubURL = &suburl mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) - registryDirectory, _ := NewRegistryDirectory(®url, mockRegistry) - - go registryDirectory.Subscribe(common.NewURLWithOptions(common.WithPath("testservice"))) + dir, _ := NewRegistryDirectory(®url, mockRegistry) + go dir.(*RegistryDirectory).subscribe(common.NewURLWithOptions(common.WithPath("testservice"))) //for group1 urlmap := url.Values{} urlmap.Set(constant.GROUP_KEY, "group1") @@ -101,7 +106,7 @@ func TestSubscribe_Group(t *testing.T) { } time.Sleep(1e9) - assert.Len(t, registryDirectory.cacheInvokers, 2) + assert.Len(t, dir.(*RegistryDirectory).cacheInvokers, 2) } func Test_Destroy(t *testing.T) { @@ -170,10 +175,24 @@ Loop1: break Loop1 } } +} +func Test_toGroupInvokers(t *testing.T) { + registryDirectory, _ := normalRegistryDir() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + invoker := mock.NewMockInvoker(ctrl) + newUrl, _ := common.NewURL("dubbo://192.168.1.1:20000/com.ikurento.user.UserProvider") + invoker.EXPECT().GetUrl().Return(newUrl).AnyTimes() + + registryDirectory.cacheInvokersMap.Store("group1", invoker) + registryDirectory.cacheInvokersMap.Store("group2", invoker) + registryDirectory.cacheInvokersMap.Store("group1", invoker) + groupInvokers := registryDirectory.toGroupInvokers() + assert.Len(t, groupInvokers, 2) } -func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockRegistry) { +func normalRegistryDir(noMockEvent ...bool) (*RegistryDirectory, *registry.MockRegistry) { extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter) url, _ := common.NewURL("mock://127.0.0.1:1111") @@ -185,9 +204,9 @@ func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockR ) url.SubURL = &suburl mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) - registryDirectory, _ := NewRegistryDirectory(&url, mockRegistry) + dir, _ := NewRegistryDirectory(&url, mockRegistry) - go registryDirectory.Subscribe(&suburl) + go dir.(*RegistryDirectory).subscribe(&suburl) if len(noMockEvent) == 0 { for i := 0; i < 3; i++ { mockRegistry.(*registry.MockRegistry).MockEvent( @@ -201,5 +220,5 @@ func normalRegistryDir(noMockEvent ...bool) (*registryDirectory, *registry.MockR ) } } - return registryDirectory, mockRegistry.(*registry.MockRegistry) + return dir.(*RegistryDirectory), mockRegistry.(*registry.MockRegistry) } diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index e691ae3cf1204ee97f130764496a7fc5bf67ac42..f27e7ce8ba5ab3515f8290b208b4823872ba48a3 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -24,9 +24,9 @@ import ( ) import ( + "github.com/coreos/etcd/embed" "github.com/dubbogo/getty" "github.com/stretchr/testify/suite" - "go.etcd.io/etcd/embed" ) import ( diff --git a/registry/etcdv3/registry.go b/registry/etcdv3/registry.go index e1c25768119ea7d7122b9aa22a5f881db44bafd9..a65d090349b40d473c769e3130e4f000ee03bd00 100644 --- a/registry/etcdv3/registry.go +++ b/registry/etcdv3/registry.go @@ -114,6 +114,10 @@ func (r *etcdV3Registry) DoRegister(root string, node string) error { return r.client.Create(path.Join(root, node), "") } +func (r *etcdV3Registry) DoUnregister(root string, node string) error { + return perrors.New("DoUnregister is not support in etcdV3Registry") +} + func (r *etcdV3Registry) CloseAndNilClient() { r.client.Close() r.client = nil @@ -164,9 +168,11 @@ func (r *etcdV3Registry) DoSubscribe(svc *common.URL) (registry.Listener, error) //register the svc to dataListener r.dataListener.AddInterestedURL(svc) - for _, v := range strings.Split(svc.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), ",") { - go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, svc.Service()), r.dataListener) - } + go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, svc.Service()), r.dataListener) 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/registry_test.go b/registry/etcdv3/registry_test.go index dc4e382979d910e4f42453fa0a409afbcb0ecabc..164fe9ca61793614e3d9d2f77f49cdfaebdd7317 100644 --- a/registry/etcdv3/registry_test.go +++ b/registry/etcdv3/registry_test.go @@ -63,7 +63,7 @@ func (suite *RegistryTestSuite) TestRegister() { if err != nil { t.Fatal(err) } - assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-1.3.0%26.*provider", children) + assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26cluster%3Dmock", children) assert.NoError(t, err) } diff --git a/registry/event.go b/registry/event.go index 0500cc70188b5cf59a2058fee224106fea06001a..6f647185cc213b80b9ab25b4702f91b36aa8ad4b 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" ) @@ -42,50 +43,31 @@ type ServiceEvent struct { Service common.URL } +// String return the description of event 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 +// ServiceInstancesChangedEvent represents service instances make some changing +type ServiceInstancesChangedEvent struct { + observer.BaseEvent + ServiceName string + Instances []ServiceInstance } -// String return a human readable string representing this event -func (b *baseEvent) String() string { - return fmt.Sprintf("baseEvent[source = %#v]", b.source) +// String return the description of the event +func (s *ServiceInstancesChangedEvent) String() string { + return fmt.Sprintf("ServiceInstancesChangedEvent[source=%s]", s.ServiceName) } -func newBaseEvent(source interface{}) *baseEvent { - return &baseEvent{ - source: source, - timestamp: time.Now(), +// NewServiceInstancesChangedEvent will create the ServiceInstanceChangedEvent instance +func NewServiceInstancesChangedEvent(serviceName string, instances []ServiceInstance) *ServiceInstancesChangedEvent { + return &ServiceInstancesChangedEvent{ + BaseEvent: observer.BaseEvent{ + Source: serviceName, + Timestamp: time.Now(), + }, + ServiceName: serviceName, + Instances: instances, } } - -// ServiceInstancesChangedEvent represents service instances make some changing -type ServiceInstancesChangedEvent struct { - fmt.Stringer - baseEvent -} diff --git a/registry/event/customizable_service_instance_listener.go b/registry/event/customizable_service_instance_listener.go new file mode 100644 index 0000000000000000000000000000000000000000..89d1621974e32a295f90e5f6c08ab17f3fc2e319 --- /dev/null +++ b/registry/event/customizable_service_instance_listener.go @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "reflect" + "sync" + + "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 +var customizableServiceInstanceListenerOnce sync.Once + +func GetCustomizableServiceInstanceListener() observer.EventListener { + customizableServiceInstanceListenerOnce.Do(func() { + customizableServiceInstanceListenerInstance = &customizableServiceInstanceListener{} + }) + return customizableServiceInstanceListenerInstance +} 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..21bddb7f0419e3ae80a4db46a6bd5f6b86b3f19b --- /dev/null +++ b/registry/event/event_publishing_service_deiscovery_test.go @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + + "github.com/apache/dubbo-go/config" + _ "github.com/apache/dubbo-go/metadata/service/inmemory" +) + +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/registry" +) + +func TestEventPublishingServiceDiscovery_DispatchEvent(t *testing.T) { + + // extension.SetMetadataService("local", inmemory.NewMetadataService) + + config.GetApplicationConfig().MetadataType = "local" + + 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 +} diff --git a/registry/event/event_publishing_service_discovery.go b/registry/event/event_publishing_service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..496eb9b4a51f1451fb4e4200325108d6dcd08b75 --- /dev/null +++ b/registry/event/event_publishing_service_discovery.go @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metadata/service" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + "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 +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 delegate function +func (epsd *EventPublishingServiceDiscovery) Update(instance registry.ServiceInstance) error { + f := func() error { + return epsd.serviceDiscovery.Update(instance) + } + return epsd.executeWithEvents(nil, f, nil) +} + +// Unregister delegate function +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)) +} + +func (epsd *EventPublishingServiceDiscovery) GetDefaultPageSize() int { + return epsd.serviceDiscovery.GetDefaultPageSize() +} + +func (epsd *EventPublishingServiceDiscovery) GetServices() *gxset.HashSet { + return epsd.serviceDiscovery.GetServices() +} + +func (epsd *EventPublishingServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + return epsd.serviceDiscovery.GetInstances(serviceName) +} + +func (epsd *EventPublishingServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + return epsd.serviceDiscovery.GetInstancesByPage(serviceName, offset, pageSize) +} + +func (epsd *EventPublishingServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + return epsd.serviceDiscovery.GetHealthyInstancesByPage(serviceName, offset, pageSize, healthy) +} + +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) +} + +func (epsd *EventPublishingServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return epsd.DispatchEventByServiceName(serviceName) +} + +func (epsd *EventPublishingServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return epsd.serviceDiscovery.DispatchEventForInstances(serviceName, instances) +} + +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 +} + +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..a06d5e4499284c66017ebb7484e4ee46ad164f5d --- /dev/null +++ b/registry/event/log_event_listener.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 event + +import ( + "reflect" + "sync" + + "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..f142168b65759455a1c46e3aa8dbb537298443bf --- /dev/null +++ b/registry/event/log_event_listener_test.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 event + +import ( + "testing" + + "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..06278f4e7793c8a0c36b9264ede896c1d6243cc8 --- /dev/null +++ b/registry/event/metadata_service_url_params_customizer.go @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "encoding/json" + + gxset "github.com/dubbogo/gost/container/set" + + "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/protocol_ports_metadata_customizer.go b/registry/event/protocol_ports_metadata_customizer.go new file mode 100644 index 0000000000000000000000000000000000000000..dd7f7678fc7f61bc85866a5642984835c9f91674 --- /dev/null +++ b/registry/event/protocol_ports_metadata_customizer.go @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + + "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 will +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... + 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) +} + +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) +} + +type endpoint struct { + Port int `json:"port"` + Protocol string `json:"protocol"` +} diff --git a/metadata/namemapping/memory/service_name_mapping.go b/registry/event/service_config_exported_event.go similarity index 65% rename from metadata/namemapping/memory/service_name_mapping.go rename to registry/event/service_config_exported_event.go index 8a891491bdb97808b77422092a1043c1c0ffafbf..7946609acdffa0e166ffc3559bd931114fa2c5d5 100644 --- a/metadata/namemapping/memory/service_name_mapping.go +++ b/registry/event/service_config_exported_event.go @@ -15,22 +15,26 @@ * limitations under the License. */ -package memory +package event import ( - gxset "github.com/dubbogo/gost/container/set" -) + "time" -import ( + "github.com/apache/dubbo-go/common/observer" "github.com/apache/dubbo-go/config" ) -type InMemoryServiceNameMapping struct{} - -func (i InMemoryServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error { - return nil +type ServiceConfigExportedEvent struct { + observer.BaseEvent + ServiceConfig *config.ServiceConfig } -func (i InMemoryServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) { - return gxset.NewSet(config.GetApplicationConfig().Name), nil +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..74f6c5f19dd4b4cfb5ceaae4010df1d49b03aa41 --- /dev/null +++ b/registry/event/service_discovery_event.go @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/registry" +) + +type ServiceDiscoveryEvent struct { + observer.BaseEvent + original registry.ServiceDiscovery +} + +func NewServiceDiscoveryEvent(discovery registry.ServiceDiscovery, original registry.ServiceDiscovery) *ServiceDiscoveryEvent { + return &ServiceDiscoveryEvent{ + BaseEvent: *observer.NewBaseEvent(discovery), + original: original, + } +} + +func (sde *ServiceDiscoveryEvent) GetServiceDiscovery() registry.ServiceDiscovery { + return sde.GetSource().(registry.ServiceDiscovery) +} + +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..650b2e8e29e23498a49f11b58aa53b018ca42e67 --- /dev/null +++ b/registry/event/service_instance_event.go @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package event + +import ( + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/registry" +) + +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, + } +} + +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..68cf588c660d6106fbba8e45a896666a3ec3fe0a --- /dev/null +++ b/registry/event/service_name_mapping_listener.go @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + + perrors "github.com/pkg/errors" + + "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(GetCustomizableServiceInstanceListener) +} + +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 +} + +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 +} + +func (s *serviceNameMappingListener) GetEventType() reflect.Type { + return reflect.TypeOf(&ServiceConfigExportedEvent{}) +} + +var ( + serviceNameMappingListenerInstance *serviceNameMappingListener + serviceNameMappingListenerOnce sync.Once +) + +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..fb1cda01a59537b9cfb850dacbbb661edeade428 --- /dev/null +++ b/registry/event/service_revision_customizer.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 event + +import ( + "fmt" + "hash/crc32" + "sort" + + "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 +} + +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 +} + +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 810420319b8cb83edeb82203142d7207a5a6bb6b..1cd5ad43a66acc70c6a7938f8d6532346fd6410d 100644 --- a/registry/event_listener.go +++ b/registry/event_listener.go @@ -18,24 +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 +// On ServiceInstancesChangedEvent the service instances change event +func (lstn *ServiceInstancesChangedListener) OnEvent(e observer.Event) error { + lstn.ChangedNotify.Notify(e) + return nil } -// TODO (implement ConditionalEventListener) -type ServiceInstancesChangedListener struct { +// 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 +} + +// get listener priority +func (lstn *ServiceInstancesChangedListener) GetPriority() int { + return -1 +} + +// get event type +func (lstn *ServiceInstancesChangedListener) GetEventType() reflect.Type { + return reflect.TypeOf(&ServiceInstancesChangedEvent{}) } diff --git a/registry/inmemory/service_discovery.go b/registry/inmemory/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..f7c3ef3bb566e81587d3845c33ce7fb799b2cd43 --- /dev/null +++ b/registry/inmemory/service_discovery.go @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 ( + "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/page" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/registry" +) + +const ( + name = "in-memory" +) + +func init() { + + instance := &InMemoryServiceDiscovery{ + instances: make(map[string]registry.ServiceInstance, 4), + listeners: make([]*registry.ServiceInstancesChangedListener, 0, 2), + } + + extension.SetServiceDiscovery(name, func(name string) (discovery registry.ServiceDiscovery, err error) { + return instance, nil + }) +} + +// InMemoryServiceDiscovery is an implementation based on memory. +// Usually you will not use this implementation except for tests. +type InMemoryServiceDiscovery struct { + instances map[string]registry.ServiceInstance + listeners []*registry.ServiceInstancesChangedListener +} + +func (i *InMemoryServiceDiscovery) String() string { + return name +} + +// Destroy doesn't destroy the instance, it just clear the instances +func (i *InMemoryServiceDiscovery) Destroy() error { + // reset to empty + i.instances = make(map[string]registry.ServiceInstance, 4) + i.listeners = make([]*registry.ServiceInstancesChangedListener, 0, 2) + return nil +} + +// Register will store the instance using its id as key +func (i *InMemoryServiceDiscovery) Register(instance registry.ServiceInstance) error { + i.instances[instance.GetId()] = instance + return nil +} + +// Update will act like register +func (i *InMemoryServiceDiscovery) Update(instance registry.ServiceInstance) error { + return i.Register(instance) +} + +// Unregister will remove the instance +func (i *InMemoryServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + delete(i.instances, instance.GetId()) + return nil +} + +// GetDefaultPageSize will return the default page size +func (i *InMemoryServiceDiscovery) GetDefaultPageSize() int { + return registry.DefaultPageSize +} + +// GetServices will return all service names +func (i *InMemoryServiceDiscovery) GetServices() *gxset.HashSet { + result := gxset.NewSet() + for _, value := range i.instances { + result.Add(value.GetServiceName()) + } + return result +} + +// GetInstances will find out all instances with serviceName +func (i *InMemoryServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + result := make([]registry.ServiceInstance, 0, len(i.instances)) + for _, value := range i.instances { + if value.GetServiceName() == serviceName { + result = append(result, value) + } + } + return result +} + +// GetInstancesByPage will return the part of instances +func (i *InMemoryServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + instances := i.GetInstances(serviceName) + // we can not use []registry.ServiceInstance since New(...) received []interface{} as parameter + result := make([]interface{}, 0, pageSize) + for i := offset; i < len(instances) && i < offset+pageSize; i++ { + result = append(result, instances[i]) + } + return gxpage.New(offset, pageSize, result, len(instances)) +} + +// GetHealthyInstancesByPage will return the instances +func (i *InMemoryServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + instances := i.GetInstances(serviceName) + // we can not use []registry.ServiceInstance since New(...) received []interface{} as parameter + result := make([]interface{}, 0, pageSize) + count := 0 + for i := offset; i < len(instances) && count < pageSize; i++ { + if instances[i].IsHealthy() == healthy { + result = append(result, instances[i]) + count++ + } + } + return gxpage.New(offset, pageSize, result, len(instances)) +} + +// GetRequestInstances will iterate the serviceName and aggregate them +func (i *InMemoryServiceDiscovery) 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] = i.GetInstancesByPage(name, offset, requestedSize) + } + return res +} + +// AddListener will save the listener inside the memory +func (i *InMemoryServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + i.listeners = append(i.listeners, listener) + return nil +} + +// DispatchEventByServiceName will do nothing +func (i *InMemoryServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return nil +} + +// DispatchEventForInstances will do nothing +func (i *InMemoryServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return nil +} + +// DispatchEvent will do nothing +func (i *InMemoryServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + return nil +} diff --git a/registry/inmemory/service_discovery_test.go b/registry/inmemory/service_discovery_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fac4699913000c44a566e6a84f850150046f8ce0 --- /dev/null +++ b/registry/inmemory/service_discovery_test.go @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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/extension" + "github.com/apache/dubbo-go/registry" +) + +func TestInMemoryServiceDiscovery(t *testing.T) { + discovery, _ := extension.GetServiceDiscovery(name, "in") + serviceName := "my-service" + err := discovery.Register(®istry.DefaultServiceInstance{ + ServiceName: serviceName, + Id: "1", + Healthy: true, + }) + assert.Nil(t, err) + + err = discovery.Register(®istry.DefaultServiceInstance{ + Id: "2", + ServiceName: "mock-service", + Healthy: false, + }) + + assert.Nil(t, err) + + services := discovery.GetServices() + assert.Equal(t, 2, services.Size()) + assert.Equal(t, registry.DefaultPageSize, discovery.GetDefaultPageSize()) + + reqInstances := discovery.GetRequestInstances([]string{serviceName, "mock-service"}, 0, 10) + assert.Equal(t, 2, len(reqInstances)) + + page := discovery.GetInstancesByPage(serviceName, 0, 10) + assert.Equal(t, 1, page.GetDataSize()) + + discovery.GetHealthyInstancesByPage(serviceName, 0, 10, true) + page = discovery.GetInstancesByPage(serviceName, 0, 10) + assert.Equal(t, 1, page.GetDataSize()) + + err = discovery.AddListener(®istry.ServiceInstancesChangedListener{}) + assert.Nil(t, err) + + err = discovery.DispatchEvent(®istry.ServiceInstancesChangedEvent{}) + assert.Nil(t, err) + + err = discovery.DispatchEventForInstances(serviceName, nil) + assert.Nil(t, err) + + err = discovery.DispatchEventByServiceName(serviceName) + assert.Nil(t, err) + + err = discovery.Unregister(®istry.DefaultServiceInstance{ + Id: "2", + }) + assert.Nil(t, err) + + services = discovery.GetServices() + assert.Equal(t, 1, services.Size()) + + err = discovery.Update(®istry.DefaultServiceInstance{ + Id: "3", + }) + assert.Nil(t, err) + + services = discovery.GetServices() + assert.Equal(t, 2, services.Size()) + + err = discovery.Destroy() + assert.Nil(t, err) + + services = discovery.GetServices() + assert.Equal(t, 0, services.Size()) +} diff --git a/registry/kubernetes/listener.go b/registry/kubernetes/listener.go index f8869fea7b77541eb929624cc1fa708c1218d7dd..ac6f8af8a2dbe5b072a580a65dadb3902eb7b750 100644 --- a/registry/kubernetes/listener.go +++ b/registry/kubernetes/listener.go @@ -103,7 +103,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { return nil, perrors.New("listener stopped") case e := <-l.events: - logger.Infof("got kubernetes event %#v", e) + logger.Debugf("got kubernetes event %#v", e) if e.ConfigType == remoting.EventTypeDel && !l.registry.client.Valid() { select { case <-l.registry.Done(): diff --git a/registry/kubernetes/listener_test.go b/registry/kubernetes/listener_test.go index c50b5b670a5491b9813652f7aa46bec18a35a7d7..ccaaf80907f9eb3d4956758374f518c66fa613d5 100644 --- a/registry/kubernetes/listener_test.go +++ b/registry/kubernetes/listener_test.go @@ -18,24 +18,15 @@ package kubernetes import ( - "encoding/json" - "os" - "strconv" "testing" - "time" ) import ( "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" ) import ( "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/config_center" "github.com/apache/dubbo-go/remoting" ) @@ -184,67 +175,7 @@ type MockDataListener struct{} func (*MockDataListener) Process(configType *config_center.ConfigChangeEvent) {} -type KubernetesRegistryTestSuite struct { - suite.Suite - - currentPod v1.Pod -} - -func (s *KubernetesRegistryTestSuite) initRegistry() *kubernetesRegistry { - - t := s.T() - - regurl, err := common.NewURL("registry://127.0.0.1:443", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) - if err != nil { - t.Fatal(err) - } - - mock, err := newMockKubernetesRegistry(®url, s.currentPod.GetNamespace(), func() (kubernetes.Interface, error) { - - out := fake.NewSimpleClientset() - - // mock current pod - if _, err := out.CoreV1().Pods(s.currentPod.GetNamespace()).Create(&s.currentPod); err != nil { - t.Fatal(err) - } - return out, nil - }) - if err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - return mock.(*kubernetesRegistry) -} - -func (s *KubernetesRegistryTestSuite) SetupSuite() { - - t := s.T() - - const ( - // kubernetes inject the var - podNameKey = "HOSTNAME" - nameSpaceKey = "NAMESPACE" - ) - - // 1. install test data - if err := json.Unmarshal([]byte(clientPodJsonData), &s.currentPod); err != nil { - t.Fatal(err) - } - - // 2. set downward-api inject env - if err := os.Setenv(podNameKey, s.currentPod.GetName()); err != nil { - t.Fatal(err) - } - if err := os.Setenv(nameSpaceKey, s.currentPod.GetNamespace()); err != nil { - t.Fatal(err) - } - -} - -func (s *KubernetesRegistryTestSuite) TestDataChange() { - - t := s.T() +func TestDataChange(t *testing.T) { listener := NewRegistryDataListener(&MockDataListener{}) url, _ := common.NewURL("jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100") @@ -253,7 +184,3 @@ func (s *KubernetesRegistryTestSuite) TestDataChange() { t.Fatal("data change not ok") } } - -func TestKubernetesRegistrySuite(t *testing.T) { - suite.Run(t, &KubernetesRegistryTestSuite{}) -} diff --git a/registry/kubernetes/registry.go b/registry/kubernetes/registry.go index 7212a83d63b34cc08f193994f004efccf9e21a0c..6fa11934652ec74c0346aaa2e79f8fa2b0b21a0f 100644 --- a/registry/kubernetes/registry.go +++ b/registry/kubernetes/registry.go @@ -21,7 +21,6 @@ import ( "fmt" "os" "path" - "strings" "sync" "time" ) @@ -30,7 +29,7 @@ import ( "github.com/dubbogo/getty" "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" - k8s "k8s.io/client-go/kubernetes" + v1 "k8s.io/api/core/v1" ) import ( @@ -108,6 +107,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") +} + func (r *kubernetesRegistry) DoSubscribe(svc *common.URL) (registry.Listener, error) { var ( @@ -135,13 +138,15 @@ func (r *kubernetesRegistry) DoSubscribe(svc *common.URL) (registry.Listener, er //register the svc to dataListener r.dataListener.AddInterestedURL(svc) - for _, v := range strings.Split(svc.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), ",") { - go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, svc.Service()), r.dataListener) - } + go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, svc.Service()), r.dataListener) return configListener, nil } +func (r *kubernetesRegistry) DoUnsubscribe(conf *common.URL) (registry.Listener, error) { + return nil, perrors.New("DoUnsubscribe is not support in kubernetesRegistry") +} + func (r *kubernetesRegistry) InitListeners() { r.listener = kubernetes.NewEventListener(r.client) r.configListener = NewConfigurationListener(r) @@ -163,15 +168,14 @@ func newKubernetesRegistry(url *common.URL) (registry.Registry, error) { go r.HandleClientRestart() r.InitListeners() - logger.Debugf("the kubernetes registry started") + logger.Debugf("kubernetes registry started") return r, nil } func newMockKubernetesRegistry( url *common.URL, - namespace string, - clientGeneratorFunc func() (k8s.Interface, error), + podsList *v1.PodList, ) (registry.Registry, error) { var err error @@ -179,7 +183,7 @@ func newMockKubernetesRegistry( r := &kubernetesRegistry{} r.InitBaseRegistry(url, r) - r.client, err = kubernetes.NewMockClient(namespace, clientGeneratorFunc) + r.client, err = kubernetes.NewMockClient(podsList) if err != nil { return nil, perrors.WithMessage(err, "new mock client") } diff --git a/registry/kubernetes/registry_test.go b/registry/kubernetes/registry_test.go index ea6d7663a9ceeab241c7a94a91f94288ab2990fc..347dadcd2c462e3a1caf9829b051a665ec61e8e3 100644 --- a/registry/kubernetes/registry_test.go +++ b/registry/kubernetes/registry_test.go @@ -18,12 +18,17 @@ package kubernetes import ( + "encoding/json" + "os" "strconv" + "sync" + "testing" "time" ) import ( "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" ) import ( @@ -31,11 +36,212 @@ import ( "github.com/apache/dubbo-go/common/constant" ) -func (s *KubernetesRegistryTestSuite) TestRegister() { +var clientPodListJsonData = `{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "dubbo.io/annotation": "W3siayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzL2R1YmJvJTNBJTJGJTJGMTcyLjE3LjAuNiUzQTIwMDAwJTJGVXNlclByb3ZpZGVyJTNGYWNjZXNzbG9nJTNEJTI2YW55aG9zdCUzRHRydWUlMjZhcHAudmVyc2lvbiUzRDAuMC4xJTI2YXBwbGljYXRpb24lM0RCRFRTZXJ2aWNlJTI2YXV0aCUzRCUyNmJlYW4ubmFtZSUzRFVzZXJQcm92aWRlciUyNmNsdXN0ZXIlM0RmYWlsb3ZlciUyNmVudmlyb25tZW50JTNEZGV2JTI2ZXhlY3V0ZS5saW1pdCUzRCUyNmV4ZWN1dGUubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNmdyb3VwJTNEJTI2aW50ZXJmYWNlJTNEY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyJTI2aXAlM0QxNzIuMTcuMC42JTI2bG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIubG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIucmV0cmllcyUzRDElMjZtZXRob2RzLkdldFVzZXIudHBzLmxpbWl0LmludGVydmFsJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5yYXRlJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNm1ldGhvZHMuR2V0VXNlci53ZWlnaHQlM0QwJTI2bW9kdWxlJTNEZHViYm9nbyUyQnVzZXItaW5mbyUyQnNlcnZlciUyNm5hbWUlM0RCRFRTZXJ2aWNlJTI2b3JnYW5pemF0aW9uJTNEaWt1cmVudG8uY29tJTI2b3duZXIlM0RaWCUyNnBhcmFtLnNpZ24lM0QlMjZwaWQlM0Q2JTI2cmVnaXN0cnkucm9sZSUzRDMlMjZyZWxlYXNlJTNEZHViYm8tZ29sYW5nLTEuMy4wJTI2cmV0cmllcyUzRCUyNnNlcnZpY2UuZmlsdGVyJTNEZWNobyUyNTJDdG9rZW4lMjUyQ2FjY2Vzc2xvZyUyNTJDdHBzJTI1MkNnZW5lcmljX3NlcnZpY2UlMjUyQ2V4ZWN1dGUlMjUyQ3BzaHV0ZG93biUyNnNpZGUlM0Rwcm92aWRlciUyNnRpbWVzdGFtcCUzRDE1OTExNTYxNTUlMjZ0cHMubGltaXQuaW50ZXJ2YWwlM0QlMjZ0cHMubGltaXQucmF0ZSUzRCUyNnRwcy5saW1pdC5yZWplY3RlZC5oYW5kbGVyJTNEJTI2dHBzLmxpbWl0LnN0cmF0ZWd5JTNEJTI2dHBzLmxpbWl0ZXIlM0QlMjZ2ZXJzaW9uJTNEJTI2d2FybXVwJTNEMTAwIiwidiI6IiJ9XQ==" + }, + "creationTimestamp": "2020-06-03T03:49:14Z", + "generateName": "server-84c864f5bc-", + "labels": { + "dubbo.io/label": "dubbo.io-value", + "pod-template-hash": "84c864f5bc", + "role": "server" + }, + "name": "server-84c864f5bc-r8qvz", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "server-84c864f5bc", + "uid": "fa376dbb-4f37-4705-8e80-727f592c19b3" + } + ], + "resourceVersion": "517460", + "selfLink": "/api/v1/namespaces/default/pods/server-84c864f5bc-r8qvz", + "uid": "f4fc811c-200c-4445-8d4f-532144957dcc" + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "DUBBO_NAMESPACE", + "value": "default" + }, + { + "name": "NAMESPACE", + "valueFrom": { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + } + } + } + ], + "image": "192.168.240.101:5000/scott/go-server", + "imagePullPolicy": "Always", + "name": "server", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "dubbo-sa-token-5qbtb", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "dubbo-sa", + "serviceAccountName": "dubbo-sa", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "dubbo-sa-token-5qbtb", + "secret": { + "defaultMode": 420, + "secretName": "dubbo-sa-token-5qbtb" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:14Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:15Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:15Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:14Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://b6421e05ce44f8a1c4fa6b72274980777c7c0f945516209f7c0558cd0cd65406", + "image": "192.168.240.101:5000/scott/go-server:latest", + "imageID": "docker-pullable://192.168.240.101:5000/scott/go-server@sha256:4eecf895054f0ff93d80db64992a561d10504e55582def6dcb6093a6d6d92461", + "lastState": {}, + "name": "server", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-06-03T03:49:15Z" + } + } + } + ], + "hostIP": "10.0.2.15", + "phase": "Running", + "podIP": "172.17.0.6", + "podIPs": [ + { + "ip": "172.17.0.6" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-06-03T03:49:14Z" + } + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } +} +` + +func getTestRegistry(t *testing.T) *kubernetesRegistry { - t := s.T() + const ( + podNameKey = "HOSTNAME" + nameSpaceKey = "NAMESPACE" + needWatchedNameSpaceKey = "DUBBO_NAMESPACE" + ) + pl := &v1.PodList{} + // 1. install test data + if err := json.Unmarshal([]byte(clientPodListJsonData), &pl); err != nil { + t.Fatal(err) + } + currentPod := pl.Items[0] - r := s.initRegistry() + env := map[string]string{ + nameSpaceKey: currentPod.GetNamespace(), + podNameKey: currentPod.GetName(), + needWatchedNameSpaceKey: "default", + } + + for k, v := range env { + if err := os.Setenv(k, v); err != nil { + t.Fatal(err) + } + } + + regurl, err := common.NewURL("registry://127.0.0.1:443", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) + if err != nil { + t.Fatal(err) + } + out, err := newMockKubernetesRegistry(®url, pl) + if err != nil { + t.Fatal(err) + } + + return out.(*kubernetesRegistry) +} + +func TestRegister(t *testing.T) { + + r := getTestRegistry(t) defer r.Destroy() url, _ := common.NewURL( @@ -52,41 +258,44 @@ func (s *KubernetesRegistryTestSuite) TestRegister() { } } -func (s *KubernetesRegistryTestSuite) TestSubscribe() { +func TestSubscribe(t *testing.T) { - t := s.T() - - r := s.initRegistry() + r := getTestRegistry(t) defer r.Destroy() - url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + url, err := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) + if err != nil { + t.Fatal(err) + } listener, err := r.DoSubscribe(&url) if err != nil { t.Fatal(err) } - time.Sleep(1e9) + wg := sync.WaitGroup{} + wg.Add(1) go func() { - err := r.Register(url) - if err != nil { - t.Fatal(err) + + defer wg.Done() + registerErr := r.Register(url) + if registerErr != nil { + t.Fatal(registerErr) } }() + wg.Wait() + serviceEvent, err := listener.Next() if err != nil { t.Fatal(err) } - - t.Logf("got event %s", serviceEvent) + t.Logf("get service event %s", serviceEvent) } -func (s *KubernetesRegistryTestSuite) TestConsumerDestroy() { - - t := s.T() +func TestConsumerDestroy(t *testing.T) { - r := s.initRegistry() + r := getTestRegistry(t) url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), @@ -105,11 +314,9 @@ func (s *KubernetesRegistryTestSuite) TestConsumerDestroy() { } -func (s *KubernetesRegistryTestSuite) TestProviderDestroy() { +func TestProviderDestroy(t *testing.T) { - t := s.T() - - r := s.initRegistry() + r := getTestRegistry(t) url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), @@ -122,9 +329,7 @@ func (s *KubernetesRegistryTestSuite) TestProviderDestroy() { assert.Equal(t, false, r.IsAvailable()) } -func (s *KubernetesRegistryTestSuite) TestNewRegistry() { - - t := s.T() +func TestNewRegistry(t *testing.T) { regUrl, err := common.NewURL("registry://127.0.0.1:443", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) @@ -137,9 +342,9 @@ func (s *KubernetesRegistryTestSuite) TestNewRegistry() { } } -func (s *KubernetesRegistryTestSuite) TestHandleClientRestart() { +func TestHandleClientRestart(t *testing.T) { - r := s.initRegistry() + r := getTestRegistry(t) r.WaitGroup().Add(1) go r.HandleClientRestart() time.Sleep(timeSecondDuration(1)) diff --git a/registry/mock_registry.go b/registry/mock_registry.go index 9591928eebd22bf2a99ec9dcfeb285c4519a3b90..f39490a26755a96aab1438d965bd8ee6fc75006f 100644 --- a/registry/mock_registry.go +++ b/registry/mock_registry.go @@ -51,6 +51,11 @@ func (*MockRegistry) Register(url common.URL) error { return nil } +// UnRegister +func (r *MockRegistry) UnRegister(conf common.URL) error { + return nil +} + // Destroy ... func (r *MockRegistry) Destroy() { if r.destroyed.CAS(false, true) { @@ -72,7 +77,7 @@ func (r *MockRegistry) subscribe(*common.URL) (Listener, error) { } // Subscribe ... -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 965e91e894ac61562bfd25c8f564f789afd6c8a1..2b6cab45c2f5b552738bce3a352e774aa4b8cbcd 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -23,13 +23,14 @@ import ( "strconv" "strings" "time" -) -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" +) + +import ( + gxnet "github.com/dubbogo/gost/net" "github.com/nacos-group/nacos-sdk-go/vo" perrors "github.com/pkg/errors" ) @@ -47,7 +48,7 @@ var ( ) const ( - //RegistryConnDelay registry connection delay + // RegistryConnDelay registry connection delay RegistryConnDelay = 3 ) @@ -61,62 +62,6 @@ type nacosRegistry struct { namingClient naming_client.INamingClient } -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 -} - -func newNacosRegistry(url *common.URL) (registry.Registry, error) { - nacosConfig, err := getNacosConfig(url) - if err != nil { - return nil, err - } - client, err := clients.CreateNamingClient(nacosConfig) - if err != nil { - return nil, err - } - registry := nacosRegistry{ - URL: url, - namingClient: client, - } - return ®istry, nil -} - func getCategory(url common.URL) string { role, _ := strconv.Atoi(url.GetParam(constant.ROLE_KEY, strconv.Itoa(constant.NACOS_DEFAULT_ROLETYPE))) category := common.DubboNodes[role] @@ -186,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) { +// subscribe from registry +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) @@ -214,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()) @@ -222,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") } func (nr *nacosRegistry) GetUrl() common.URL { @@ -229,9 +185,69 @@ func (nr *nacosRegistry) GetUrl() common.URL { } func (nr *nacosRegistry) IsAvailable() bool { + // TODO return true } 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/registry_test.go b/registry/nacos/registry_test.go index 7475b455c0dda09da65012465711ece264bb3dd5..d0311b200b27081c60bc97b2307a54774ca977bd 100644 --- a/registry/nacos/registry_test.go +++ b/registry/nacos/registry_test.go @@ -42,7 +42,7 @@ func TestNacosRegistry_Register(t *testing.T) { urlMap.Set(constant.INTERFACE_KEY, "com.ikurento.user.UserProvider") urlMap.Set(constant.VERSION_KEY, "1.0.0") urlMap.Set(constant.CLUSTER_KEY, "mock") - url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) + testUrl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) reg, err := newNacosRegistry(®url) assert.Nil(t, err) @@ -50,7 +50,7 @@ func TestNacosRegistry_Register(t *testing.T) { t.Errorf("new nacos registry error:%s \n", err.Error()) return } - err = reg.Register(url) + err = reg.Register(testUrl) assert.Nil(t, err) if err != nil { t.Errorf("register error:%s \n", err.Error()) @@ -72,10 +72,10 @@ func TestNacosRegistry_Subscribe(t *testing.T) { urlMap.Set(constant.VERSION_KEY, "1.0.0") urlMap.Set(constant.CLUSTER_KEY, "mock") urlMap.Set(constant.NACOS_PATH_KEY, "") - url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) + testUrl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) reg, _ := newNacosRegistry(®url) - err := reg.Register(url) + err := reg.Register(testUrl) assert.Nil(t, err) if err != nil { t.Errorf("new nacos registry error:%s \n", err.Error()) @@ -84,7 +84,7 @@ func TestNacosRegistry_Subscribe(t *testing.T) { regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) reg2, _ := newNacosRegistry(®url) - listener, err := reg2.(*nacosRegistry).subscribe(&url) + listener, err := reg2.(*nacosRegistry).subscribe(&testUrl) assert.Nil(t, err) if err != nil { t.Errorf("subscribe error:%s \n", err.Error()) diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..5b952e2541d4fd61e247543195786f1141ae2a85 --- /dev/null +++ b/registry/nacos/service_discovery.go @@ -0,0 +1,340 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package nacos + +import ( + "fmt" + "sync" + + "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" + + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/remoting/nacos" +) + +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/registry" +) + +const ( + defaultGroup = constant.SERVICE_DISCOVERY_DEFAULT_GROUP + idKey = "id" +) + +// init will put the service discovery into extension +func init() { + extension.SetServiceDiscovery(constant.NACOS_KEY, newNacosServiceDiscovery) +} + +// nacosServiceDiscovery is the implementation of service discovery based on nacos. +// 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 { + 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. +// Actually, it only marks the naming client as null and then return +func (n *nacosServiceDiscovery) Destroy() error { + n.namingClient = nil + return nil +} + +// Register will register the service to nacos +func (n *nacosServiceDiscovery) Register(instance registry.ServiceInstance) error { + ins := n.toRegisterInstance(instance) + ok, err := n.namingClient.RegisterInstance(ins) + if err != nil || !ok { + return perrors.WithMessage(err, "Could not register the instance. "+instance.GetServiceName()) + } + return nil +} + +// Update will update the information +// However, because nacos client doesn't support the update API, +// so we should unregister the instance and then register it again. +// the error handling is hard to implement +func (n *nacosServiceDiscovery) Update(instance registry.ServiceInstance) error { + // TODO(wait for nacos support) + err := n.Unregister(instance) + if err != nil { + return perrors.WithStack(err) + } + return n.Register(instance) +} + +// Unregister will unregister the instance +func (n *nacosServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + ok, err := n.namingClient.DeregisterInstance(n.toDeregisterInstance(instance)) + if err != nil || !ok { + return perrors.WithMessage(err, "Could not unregister the instance. "+instance.GetServiceName()) + } + return nil +} + +// GetDefaultPageSize will return the constant registry.DefaultPageSize +func (n *nacosServiceDiscovery) GetDefaultPageSize() int { + return registry.DefaultPageSize +} + +// GetServices will return the all services +func (n *nacosServiceDiscovery) GetServices() *gxset.HashSet { + services, err := n.namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{ + GroupName: n.group, + }) + + res := gxset.NewSet() + if err != nil { + logger.Errorf("Could not query the services: %v", err) + return res + } + + for _, e := range services { + res.Add(e.Name) + } + return res +} + +// GetInstances will return the instances of serviceName and the group +func (n *nacosServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + instances, err := n.namingClient.SelectAllInstances(vo.SelectAllInstancesParam{ + ServiceName: serviceName, + GroupName: n.group, + }) + if err != nil { + logger.Errorf("Could not query the instances for service: " + serviceName + ", group: " + n.group) + return make([]registry.ServiceInstance, 0, 0) + } + res := make([]registry.ServiceInstance, 0, len(instances)) + for _, ins := range instances { + metadata := ins.Metadata + id := metadata[idKey] + + delete(metadata, idKey) + + res = append(res, ®istry.DefaultServiceInstance{ + Id: id, + ServiceName: ins.ServiceName, + Host: ins.Ip, + Port: int(ins.Port), + Enable: ins.Enable, + Healthy: ins.Healthy, + Metadata: metadata, + }) + } + + return res +} + +// GetInstancesByPage will return the instances +// Due to nacos client does not support pagination, so we have to query all instances and then return part of them +func (n *nacosServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + all := n.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 +// The nacos client has an API SelectInstances, which has a parameter call HealthyOnly. +// 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 (n *nacosServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + all := n.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 +// The nacos client doesn't have batch API, so we should query those serviceNames one by one. +func (n *nacosServiceDiscovery) 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] = n.GetInstancesByPage(name, offset, requestedSize) + } + return res +} + +// AddListener will add a listener +func (n *nacosServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + return n.namingClient.Subscribe(&vo.SubscribeParam{ + ServiceName: listener.ServiceName, + SubscribeCallback: func(services []model.SubscribeService, err error) { + if err != nil { + logger.Errorf("Could not handle the subscribe notification because the err is not nil."+ + " service name: %s, err: %v", listener.ServiceName, err) + } + instances := make([]registry.ServiceInstance, 0, len(services)) + for _, service := range services { + // we won't use the nacos instance id here but use our instance id + metadata := service.Metadata + id := metadata[idKey] + + delete(metadata, idKey) + + instances = append(instances, ®istry.DefaultServiceInstance{ + Id: id, + ServiceName: service.ServiceName, + Host: service.Ip, + Port: int(service.Port), + Enable: service.Enable, + Healthy: true, + Metadata: metadata, + }) + } + + e := n.DispatchEventForInstances(listener.ServiceName, instances) + if e != nil { + logger.Errorf("Dispatching event got exception, service name: %s, err: %v", listener.ServiceName, err) + } + }, + }) +} + +// DispatchEventByServiceName will dispatch the event for the service with the service name +func (n *nacosServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return n.DispatchEventForInstances(serviceName, n.GetInstances(serviceName)) +} + +// DispatchEventForInstances will dispatch the event to those instances +func (n *nacosServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return n.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances)) +} + +// DispatchEvent will dispatch the event +func (n *nacosServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + extension.GetGlobalDispatcher().Dispatch(event) + return nil +} + +// toRegisterInstance convert the ServiceInstance to RegisterInstanceParam +// the Ephemeral will be true +func (n *nacosServiceDiscovery) toRegisterInstance(instance registry.ServiceInstance) vo.RegisterInstanceParam { + metadata := instance.GetMetadata() + if metadata == nil { + metadata = make(map[string]string, 1) + } + metadata[idKey] = instance.GetId() + return vo.RegisterInstanceParam{ + ServiceName: instance.GetServiceName(), + Ip: instance.GetHost(), + Port: uint64(instance.GetPort()), + Metadata: metadata, + // 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, + } +} + +// toDeregisterInstance will convert the ServiceInstance to DeregisterInstanceParam +func (n *nacosServiceDiscovery) toDeregisterInstance(instance registry.ServiceInstance) vo.DeregisterInstanceParam { + return vo.DeregisterInstanceParam{ + ServiceName: instance.GetServiceName(), + Ip: instance.GetHost(), + Port: uint64(instance.GetPort()), + GroupName: n.group, + } +} + +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") + } + + 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.WithMessage(err, "create nacos client failed.") + } + + descriptor := fmt.Sprintf("nacos-service-discovery[%s]", remoteConfig.Address) + + return &nacosServiceDiscovery{ + group: group, + namingClient: client, + descriptor: descriptor, + }, nil +} diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go new file mode 100644 index 0000000000000000000000000000000000000000..633a1d41c81ed88b40db3a637333e6795abed871 --- /dev/null +++ b/registry/nacos/service_discovery_test.go @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 ( + "testing" + + "github.com/apache/dubbo-go/config" +) + +import ( + "github.com/stretchr/testify/assert" +) + +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/common/observer/dispatcher" + "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) { + prepareData() + serviceDiscovery, err := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) + assert.Nil(t, err) + assert.NotNil(t, serviceDiscovery) + err = serviceDiscovery.Destroy() + assert.Nil(t, err) + 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" + port := 123 + instance := ®istry.DefaultServiceInstance{ + Id: id, + ServiceName: serviceName, + Host: host, + Port: port, + Enable: true, + Healthy: true, + Metadata: nil, + } + + // clean data + + serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) + + // clean data for local test + serviceDiscovry.Unregister(®istry.DefaultServiceInstance{ + Id: id, + ServiceName: serviceName, + Host: host, + Port: port, + }) + + err := serviceDiscovry.Register(instance) + assert.Nil(t, err) + + page := serviceDiscovry.GetHealthyInstancesByPage(serviceName, 0, 10, true) + assert.NotNil(t, page) + + assert.Equal(t, 0, page.GetOffset()) + assert.Equal(t, 10, page.GetPageSize()) + assert.Equal(t, 1, page.GetDataSize()) + + instance = page.GetData()[0].(*registry.DefaultServiceInstance) + assert.NotNil(t, instance) + assert.Equal(t, id, instance.GetId()) + assert.Equal(t, host, instance.GetHost()) + assert.Equal(t, port, instance.GetPort()) + assert.Equal(t, serviceName, instance.GetServiceName()) + assert.Equal(t, 0, len(instance.GetMetadata())) + + instance.Metadata["a"] = "b" + + err = serviceDiscovry.Update(instance) + assert.Nil(t, err) + + pageMap := serviceDiscovry.GetRequestInstances([]string{serviceName}, 0, 1) + assert.Equal(t, 1, len(pageMap)) + page = pageMap[serviceName] + assert.NotNil(t, page) + assert.Equal(t, 1, len(page.GetData())) + + instance = page.GetData()[0].(*registry.DefaultServiceInstance) + v, _ := instance.Metadata["a"] + assert.Equal(t, "b", v) + + // test dispatcher event + err = serviceDiscovry.DispatchEventByServiceName(serviceName) + assert.Nil(t, err) + + // test AddListener + err = serviceDiscovry.AddListener(®istry.ServiceInstancesChangedListener{}) + assert.Nil(t, err) +} + +func TestNacosServiceDiscovery_GetDefaultPageSize(t *testing.T) { + prepareData() + serviceDiscovry, _ := extension.GetServiceDiscovery(constant.NACOS_KEY, testName) + assert.Equal(t, registry.DefaultPageSize, serviceDiscovry.GetDefaultPageSize()) +} + +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.go b/registry/protocol/protocol.go index a7678ba4e2f38cfeb77f202103e03066a7efdbef..a936db80bf2c3b46ba389142cc40686ed3df17b1 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -22,7 +22,6 @@ import ( "strings" "sync" ) - import ( gxset "github.com/dubbogo/gost/container/set" ) @@ -39,20 +38,26 @@ import ( "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/protocolwrapper" "github.com/apache/dubbo-go/registry" - directory2 "github.com/apache/dubbo-go/registry/directory" + _ "github.com/apache/dubbo-go/registry/directory" "github.com/apache/dubbo-go/remoting" ) var ( - regProtocol *registryProtocol + regProtocol *registryProtocol + once sync.Once + reserveParams = []string{ + "application", "codec", "exchanger", "serialization", "cluster", "connections", "deprecated", "group", + "loadbalance", "mock", "path", "timeout", "token", "version", "warmup", "weight", "timestamp", "dubbo", + "release", "interface", + } ) type registryProtocol struct { invokers []protocol.Invoker // Registry Map<RegistryAddress, Registry> registries *sync.Map - //To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed. - //providerurl <--> exporter + // To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed. + // providerurl <--> exporter bounds *sync.Map overrideListeners *sync.Map serviceConfigurationListeners *sync.Map @@ -65,10 +70,8 @@ func init() { } func getCacheKey(url *common.URL) string { - newUrl := url.Clone() delKeys := gxset.NewSet("dynamic", "enabled") - newUrl.RemoveParams(delKeys) - return newUrl.String() + return url.CloneExceptParams(delKeys).String() } func newRegistryProtocol() *registryProtocol { @@ -87,6 +90,27 @@ func getRegistry(regUrl *common.URL) registry.Registry { return reg } +func getUrlToRegistry(providerUrl *common.URL, registryUrl *common.URL) *common.URL { + if registryUrl.GetParamBool("simplified", false) { + return providerUrl.CloneWithParams(reserveParams) + } else { + return filterHideKey(providerUrl) + } +} + +// filterHideKey filter the parameters that do not need to be output in url(Starting with .) +func filterHideKey(url *common.URL) *common.URL { + + // be careful params maps in url is map type + removeSet := gxset.NewSet() + for k, _ := range url.GetParams() { + if strings.HasPrefix(k, ".") { + removeSet.Add(k) + } + } + return url.CloneExceptParams(removeSet) +} + func (proto *registryProtocol) initConfigurationListeners() { proto.overrideListeners = &sync.Map{} proto.serviceConfigurationListeners = &sync.Map{} @@ -110,8 +134,8 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { reg = regI.(registry.Registry) } - //new registry directory for store service url from registry - directory, err := directory2.NewRegistryDirectory(®istryUrl, reg) + // new registry directory for store service url from registry + directory, err := extension.GetDefaultRegistryDirectory(®istryUrl, reg) if err != nil { logger.Errorf("consumer service %v create registry directory error, error message is %s, and will return nil invoker!", serviceUrl.String(), err.Error()) @@ -123,9 +147,8 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { logger.Errorf("consumer service %v register registry %v error, error message is %s", serviceUrl.String(), registryUrl.String(), err.Error()) } - go directory.Subscribe(serviceUrl) - //new cluster invoker + // new cluster invoker cluster := extension.GetCluster(serviceUrl.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER)) invoker := cluster.Join(directory) @@ -150,7 +173,6 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte serviceConfigurationListener.OverrideUrl(providerUrl) var reg registry.Registry - if regI, loaded := proto.registries.Load(registryUrl.Key()); !loaded { reg = getRegistry(registryUrl) proto.registries.Store(registryUrl.Key(), reg) @@ -158,7 +180,8 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte reg = regI.(registry.Registry) } - err := reg.Register(*providerUrl) + registeredProviderUrl := getUrlToRegistry(providerUrl, registryUrl) + err := reg.Register(*registeredProviderUrl) if err != nil { logger.Errorf("provider service %v register registry %v error, error message is %s", providerUrl.Key(), registryUrl.Key(), err.Error()) @@ -190,7 +213,7 @@ func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common oldExporter.(protocol.Exporter).Unexport() proto.bounds.Delete(key) proto.Export(wrappedNewInvoker) - //TODO: unregister & unsubscribe + // TODO: unregister & unsubscribe } } @@ -273,7 +296,7 @@ func isMatched(providerUrl *common.URL, consumerUrl *common.URL) bool { providerGroup := providerUrl.GetParam(constant.GROUP_KEY, "") providerVersion := providerUrl.GetParam(constant.VERSION_KEY, "") providerClassifier := providerUrl.GetParam(constant.CLASSIFIER_KEY, "") - //todo: public static boolean isContains(String values, String value) { + // todo: public static boolean isContains(String values, String value) { // return isNotEmpty(values) && isContains(COMMA_SPLIT_PATTERN.split(values), value); // } return (consumerGroup == constant.ANY_VALUE || consumerGroup == providerGroup || @@ -326,9 +349,9 @@ func (proto *registryProtocol) Destroy() { } func getRegistryUrl(invoker protocol.Invoker) *common.URL { - //here add * for return a new url + // here add * for return a new url url := invoker.GetUrl() - //if the protocol == registry ,set protocol the registry value in url.params + // if the protocol == registry ,set protocol the registry value in url.params if url.Protocol == constant.REGISTRY_PROTOCOL { protocol := url.GetParam(constant.REGISTRY_KEY, "") url.Protocol = protocol @@ -338,7 +361,7 @@ func getRegistryUrl(invoker protocol.Invoker) *common.URL { func getProviderUrl(invoker protocol.Invoker) *common.URL { url := invoker.GetUrl() - //be careful params maps in url is map type + // be careful params maps in url is map type return url.SubURL.Clone() } @@ -346,12 +369,12 @@ func setProviderUrl(regURL *common.URL, providerURL *common.URL) { regURL.SubURL = providerURL } -// GetProtocol ... +// GetProtocol return the singleton RegistryProtocol func GetProtocol() protocol.Protocol { - if regProtocol != nil { - return regProtocol - } - return newRegistryProtocol() + once.Do(func() { + regProtocol = newRegistryProtocol() + }) + return regProtocol } type wrappedInvoker struct { diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go index cee2a6a625368f655d1b9bc5fe8cc37031e1aef7..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) } @@ -284,3 +287,12 @@ func TestExportWithApplicationConfig(t *testing.T) { v2, _ := regProtocol.bounds.Load(getCacheKey(newUrl)) assert.NotNil(t, v2) } + +func TestGetProviderUrlWithHideKey(t *testing.T) { + url, _ := common.NewURL("dubbo://127.0.0.1:1111?a=a1&b=b1&.c=c1&.d=d1&e=e1&protocol=registry") + providerUrl := getUrlToRegistry(&url, &url) + assert.NotContains(t, providerUrl.GetParams(), ".c") + assert.NotContains(t, providerUrl.GetParams(), ".d") + assert.Contains(t, providerUrl.GetParams(), "a") + +} diff --git a/registry/registry.go b/registry/registry.go index d673864700e6ba99e8f0283247d53760b85598aa..ce214c4971b0b00b5e300542ad9172331a0b3d02 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 } // NotifyListener ... diff --git a/registry/service_discovery.go b/registry/service_discovery.go index 5577f52930c7639afca851f7390779ff6e690c6e..a8228a4abe8ed07e3c5afda300702f778daea4ae 100644 --- a/registry/service_discovery.go +++ b/registry/service_discovery.go @@ -26,19 +26,13 @@ import ( gxpage "github.com/dubbogo/gost/page" ) -import ( - "github.com/apache/dubbo-go/common" -) +const DefaultPageSize = 100 type ServiceDiscovery interface { fmt.Stringer // ----------------- lifecycle ------------------- - // Initialize will initialize the service discovery instance - // if initialize failed, it will return the error - Initialize(url common.URL) error - // Destroy will destroy the service discovery. // If the discovery cannot be destroy, it will return an error. Destroy() error @@ -59,7 +53,7 @@ type ServiceDiscovery interface { GetDefaultPageSize() int // GetServices will return the all service names. - GetServices() gxset.HashSet + GetServices() *gxset.HashSet // GetInstances will return all service instances with serviceName GetInstances(serviceName string) []ServiceInstance @@ -78,6 +72,7 @@ type ServiceDiscovery interface { // ----------------- event ---------------------- // AddListener adds a new ServiceInstancesChangedListener + // see addServiceInstancesChangedListener in Java AddListener(listener *ServiceInstancesChangedListener) error // DispatchEventByServiceName dispatches the ServiceInstancesChangedEvent to service instance whose name is serviceName @@ -87,5 +82,5 @@ type ServiceDiscovery interface { DispatchEventForInstances(serviceName string, instances []ServiceInstance) error // DispatchEvent dispatches the event - DispatchEvent(event ServiceInstancesChangedEvent) error + DispatchEvent(event *ServiceInstancesChangedEvent) error } diff --git a/registry/service_instance.go b/registry/service_instance.go index d27ed8accfc4a415dc9474b22433a62779b59662..08ca79ecbf5ae96832516335f7d55fdfb2996938 100644 --- a/registry/service_instance.go +++ b/registry/service_instance.go @@ -17,6 +17,10 @@ package registry +import ( + gxsort "github.com/dubbogo/gost/sort" +) + type ServiceInstance interface { // GetId will return this instance's id. It should be unique. @@ -29,8 +33,7 @@ type ServiceInstance interface { GetHost() string // GetPort will return the port. - // if the port is not present, return error - GetPort() (int, error) + GetPort() int // IsEnable will return the enable status of this instance IsEnable() bool @@ -41,3 +44,62 @@ type ServiceInstance interface { // GetMetadata will return the metadata GetMetadata() map[string]string } + +// DefaultServiceInstance the default implementation of ServiceInstance +// or change the ServiceInstance to be struct??? +type DefaultServiceInstance struct { + Id string + ServiceName string + Host string + Port int + Enable bool + Healthy bool + Metadata map[string]string +} + +// GetId will return this instance's id. It should be unique. +func (d *DefaultServiceInstance) GetId() string { + return d.Id +} + +// GetServiceName will return the serviceName +func (d *DefaultServiceInstance) GetServiceName() string { + return d.ServiceName +} + +// GetHost will return the hostname +func (d *DefaultServiceInstance) GetHost() string { + return d.Host +} + +// GetPort will return the port. +func (d *DefaultServiceInstance) GetPort() int { + return d.Port +} + +// IsEnable will return the enable status of this instance +func (d *DefaultServiceInstance) IsEnable() bool { + return d.Enable +} + +// IsHealthy will return the value represent the instance whether healthy or not +func (d *DefaultServiceInstance) IsHealthy() bool { + return d.Healthy +} + +// 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/cluster/router/match/match_utils_test.go b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go similarity index 55% rename from cluster/router/match/match_utils_test.go rename to registry/servicediscovery/instance/random/random_service_instance_selector_test.go index f16480f1d3b7dd5ca820c81d5d04d837c129687f..cddeb42c904131cdc6a62e5142de850410a3ec5a 100644 --- a/cluster/router/match/match_utils_test.go +++ b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package match +package random import ( "testing" @@ -27,20 +27,30 @@ import ( import ( "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/registry" ) -func TestIsMatchInternalPattern(t *testing.T) { - assert.Equal(t, true, isMatchInternalPattern("*", "value")) - assert.Equal(t, true, isMatchInternalPattern("", "")) - assert.Equal(t, false, isMatchInternalPattern("", "value")) - assert.Equal(t, true, isMatchInternalPattern("value", "value")) - assert.Equal(t, true, isMatchInternalPattern("v*", "value")) - assert.Equal(t, true, isMatchInternalPattern("*ue", "value")) - assert.Equal(t, true, isMatchInternalPattern("*e", "value")) - assert.Equal(t, true, isMatchInternalPattern("v*e", "value")) -} - -func TestIsMatchGlobPattern(t *testing.T) { - url, _ := common.NewURL("dubbo://localhost:8080/Foo?key=v*e") - assert.Equal(t, true, IsMatchGlobalPattern("$key", "value", &url)) +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/config/instance/metedata_report.go b/registry/servicediscovery/instance/service_instance_selector.go similarity index 68% rename from config/instance/metedata_report.go rename to registry/servicediscovery/instance/service_instance_selector.go index cd54b0a7940df166c88f02234ab1a4e3bf384163..82fb3458be2838e9a5780e95be71aa89039b664f 100644 --- a/config/instance/metedata_report.go +++ b/registry/servicediscovery/instance/service_instance_selector.go @@ -17,25 +17,12 @@ package instance -import ( - "sync" -) - import ( "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/extension" - "github.com/apache/dubbo-go/metadata" -) - -var ( - instance metadata.MetadataReport - once sync.Once + "github.com/apache/dubbo-go/registry" ) -// GetMetadataReportInstance ... -func GetMetadataReportInstance(url *common.URL) metadata.MetadataReport { - once.Do(func() { - instance = extension.GetMetadataReportFactory(url.Protocol).CreateMetadataReport(url) - }) - return instance +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..79194fb084e85650ab271658541d15f5efc7657f --- /dev/null +++ b/registry/servicediscovery/service_discovery_registry.go @@ -0,0 +1,709 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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" + + 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" + + "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), + 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) + } + // TODO make sure it's workable + 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) + 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 serviceInsances == nil || 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"` + Protocol string `json:"protocol"` +} + +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..247cfd65eb0de5e85a4c046dbda844b2606bc3ac --- /dev/null +++ b/registry/servicediscovery/service_discovery_registry_test.go @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package servicediscovery + +import ( + "testing" +) + +var ( + SERVICE_INTERFACE = "org.apache.dubbo.metadata.MetadataService" + GROUP = "dubbo-provider" + VERSION = "1.0.0" +) + +func TestServiceDiscoveryRegistry_Register(t *testing.T) { + // registryURL,_:=event.NewURL("in-memory://localhost:12345", + // event.WithParamsValue("registry-type","service"), + // event.WithParamsValue("subscribed-services","a, b , c,d,e ,")) + // url,_:=event.NewURL("dubbo://192.168.0.102:20880/"+ SERVICE_INTERFACE + + // "?&application=" + GROUP + + // "&interface=" + SERVICE_INTERFACE + + // "&group=" + GROUP + + // "&version=" + VERSION + + // "&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs" + + // "&side=provider") + //registry,err:=newServiceDiscoveryRegistry(®istryURL) + //if err!=nil{ + // logger.Errorf("create service discovery registry catch error:%s",err.Error()) + //} + //assert.Nil(t,err) + //assert.NotNil(t,registry) + //registry.Register(url) + +} 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..a7d2f3ee98f20bf16437008135e4964195680abb --- /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..f8c76f6e84eb2ceba47481d5f856f6885525f09c --- /dev/null +++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer_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 synthesizer + +var ( + synthesizers []SubscribedURLsSynthesizer +) + +func AddSynthesizer(synthesizer SubscribedURLsSynthesizer) { + synthesizers = append(synthesizers, synthesizer) +} + +func GetAllSynthesizer() []SubscribedURLsSynthesizer { + return synthesizers +} diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index bef1760e04fd6597721fd19b5d19820f45ed2bf0..ec82fa0309118fba4b5c21772d4dfd356f3b0c5c 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -35,23 +35,38 @@ import ( zk "github.com/apache/dubbo-go/remoting/zookeeper" ) -// RegistryDataListener ... +// RegistryDataListener contains all URL information subscribed by zookeeper registry type RegistryDataListener struct { - interestedURL []*common.URL - listener config_center.ConfigurationListener + subscribed map[string]config_center.ConfigurationListener + mutex sync.Mutex + closed bool } -// NewRegistryDataListener ... -func NewRegistryDataListener(listener config_center.ConfigurationListener) *RegistryDataListener { - return &RegistryDataListener{listener: listener} +// NewRegistryDataListener constructs a new RegistryDataListener +func NewRegistryDataListener() *RegistryDataListener { + return &RegistryDataListener{ + subscribed: make(map[string]config_center.ConfigurationListener)} } -// AddInterestedURL ... -func (l *RegistryDataListener) AddInterestedURL(url *common.URL) { - l.interestedURL = append(l.interestedURL, url) +// SubscribeURL is used to set a watch listener for url +func (l *RegistryDataListener) SubscribeURL(url *common.URL, listener config_center.ConfigurationListener) { + if l.closed { + return + } + 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 ... +// DataChange accepts all events sent from the zookeeper server and trigger the corresponding listener for processing func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { // Intercept the last bit index := strings.Index(eventType.Path, "/providers/") @@ -65,10 +80,14 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { logger.Errorf("Listen NewURL(r{%s}) = error{%v} eventType.Path={%v}", url, err, eventType.Path) return false } - - for _, v := range l.interestedURL { - if serviceURL.URLEqual(*v) { - l.listener.Process( + l.mutex.Lock() + defer l.mutex.Unlock() + if l.closed { + return false + } + for serviceKey, listener := range l.subscribed { + if serviceURL.ServiceKey() == serviceKey { + listener.Process( &config_center.ConfigChangeEvent{ Key: eventType.Path, Value: serviceURL, @@ -81,38 +100,55 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { return false } -// RegistryConfigurationListener ... +// Close all RegistryConfigurationListener in subscribed +func (l *RegistryDataListener) Close() { + l.mutex.Lock() + defer l.mutex.Unlock() + for _, listener := range l.subscribed { + listener.(*RegistryConfigurationListener).Close() + } +} + +// RegistryConfigurationListener represent the processor of zookeeper watcher type RegistryConfigurationListener struct { - client *zk.ZookeeperClient - registry *zkRegistry - events chan *config_center.ConfigChangeEvent - isClosed bool - 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} + return &RegistryConfigurationListener{ + client: client, + registry: reg, + events: make(chan *config_center.ConfigChangeEvent, 32), + isClosed: false, + close: make(chan struct{}, 1), + subscribeURL: conf} } -// Process ... +// Process submit the ConfigChangeEvent to the event chan to notify all observer func (l *RegistryConfigurationListener) Process(configType *config_center.ConfigChangeEvent) { l.events <- configType } -// Next ... +// Next will observe the registry state and events chan func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) { for { select { case <-l.client.Done(): - logger.Warnf("listener's zk client connection is broken, so zk event listener exit now.") - return nil, perrors.New("listener stopped") - + logger.Warnf("listener's zk client connection (address {%s}) is broken, so zk event listener exit now.", l.client.ZkAddrs) + return nil, perrors.New("zookeeper client stopped") + case <-l.close: + return nil, perrors.New("listener have been closed") case <-l.registry.Done(): - logger.Warnf("zk consumer register has quit, so zk event listener exit now.") - return nil, perrors.New("listener stopped") - + logger.Warnf("zk consumer register has quit, so zk event listener exit now. (registry url {%v}", l.registry.BaseRegistry.URL) + return nil, perrors.New("zookeeper registry, (registry url{%v}) stopped") case e := <-l.events: logger.Debugf("got zk event %s", e) if e.ConfigType == remoting.EventTypeDel && !l.valid() { @@ -127,15 +163,17 @@ func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) { } } -// Close ... +// Close RegistryConfigurationListener only once func (l *RegistryConfigurationListener) Close() { // ensure that the listener will be closed at most once. l.closeOnce.Do(func() { l.isClosed = true + l.close <- struct{}{} l.registry.WaitGroup().Done() }) } +// valid return the true if the client conn isn't nil func (l *RegistryConfigurationListener) valid() bool { return l.client.ZkConnValid() } diff --git a/registry/zookeeper/listener_test.go b/registry/zookeeper/listener_test.go index 1a76b29a6f64e0329b289ce50218032a25f6f5cd..a0e9147a9e0ee8767efcf78d5e2aa536140f6a8b 100644 --- a/registry/zookeeper/listener_test.go +++ b/registry/zookeeper/listener_test.go @@ -32,15 +32,15 @@ import ( ) func Test_DataChange(t *testing.T) { - listener := NewRegistryDataListener(&MockDataListener{}) + listener := NewRegistryDataListener() url, _ := common.NewURL("jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100") - listener.AddInterestedURL(&url) + listener.SubscribeURL(&url, &MockConfigurationListener{}) int := listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-1.3.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"}) assert.Equal(t, true, int) } -type MockDataListener struct { +type MockConfigurationListener struct { } -func (*MockDataListener) Process(configType *config_center.ConfigChangeEvent) { +func (*MockConfigurationListener) Process(configType *config_center.ConfigChangeEvent) { } diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go index e13443d57d7dae9fb5d50b2e1c28f618780fd850..5d5f9e0526b7b8a9c5a2e2524f27f03573d758a8 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -20,7 +20,7 @@ package zookeeper import ( "fmt" "net/url" - "strings" + "path" "sync" "time" ) @@ -54,12 +54,11 @@ func init() { type zkRegistry struct { registry.BaseRegistry - client *zookeeper.ZookeeperClient - listenerLock sync.Mutex - listener *zookeeper.ZkEventListener - dataListener *RegistryDataListener - configListener *RegistryConfigurationListener - cltLock sync.Mutex + client *zookeeper.ZookeeperClient + listenerLock sync.Mutex + listener *zookeeper.ZkEventListener + dataListener *RegistryDataListener + cltLock sync.Mutex //for provider zkPath map[string]int // key = protocol://ip:port/interface } @@ -78,13 +77,12 @@ func newZkRegistry(url *common.URL) (registry.Registry, error) { if err != nil { return nil, err } - r.WaitGroup().Add(1) //zk client start successful, then wg +1 go zookeeper.HandleClientRestart(r) r.listener = zookeeper.NewZkEventListener(r.client) - r.configListener = NewRegistryConfigurationListener(r.client, r) - r.dataListener = NewRegistryDataListener(r.configListener) + + r.dataListener = NewRegistryDataListener() return r, nil } @@ -121,8 +119,32 @@ func newMockZkRegistry(url *common.URL, opts ...zookeeper.Option) (*zk.TestClust func (r *zkRegistry) InitListeners() { r.listener = zookeeper.NewZkEventListener(r.client) - r.configListener = NewRegistryConfigurationListener(r.client, r) - r.dataListener = NewRegistryDataListener(r.configListener) + newDataListener := NewRegistryDataListener() + // should recover if dataListener isn't nil before + if r.dataListener != nil { + // close all old listener + oldDataListener := r.dataListener + oldDataListener.mutex.Lock() + defer oldDataListener.mutex.Unlock() + recoverd := r.dataListener.subscribed + if recoverd != nil && len(recoverd) > 0 { + // recover all subscribed url + for _, oldListener := range recoverd { + var ( + regConfigListener *RegistryConfigurationListener + ok bool + ) + + if regConfigListener, ok = oldListener.(*RegistryConfigurationListener); ok { + regConfigListener.Close() + } + 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) + + } + } + } + r.dataListener = newDataListener } func (r *zkRegistry) CreatePath(path string) error { @@ -133,10 +155,23 @@ 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)) +} + 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) +} + func (r *zkRegistry) CloseAndNilClient() { r.client.Close() r.client = nil @@ -155,8 +190,8 @@ func (r *zkRegistry) ZkClientLock() *sync.Mutex { } func (r *zkRegistry) CloseListener() { - if r.configListener != nil { - r.configListener.Close() + if r.dataListener != nil { + r.dataListener.Close() } } @@ -173,32 +208,49 @@ func (r *zkRegistry) registerTempZookeeperNode(root string, node string) error { logger.Errorf("zk.Create(root{%s}) = err{%v}", root, perrors.WithStack(err)) return perrors.WithStack(err) } + + // try to register the node zkPath, err = r.client.RegisterTemp(root, node) if err != nil { - if err == zk.ErrNodeExists { - logger.Warnf("RegisterTempNode(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err)) - } else { - logger.Errorf("RegisterTempNode(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err)) + logger.Errorf("Register temp node(root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err)) + if perrors.Cause(err) == zk.ErrNodeExists { + // should delete the old node + logger.Info("Register temp node failed, try to delete the old and recreate (root{%s}, node{%s}) , ignore!", root, node) + if err = r.client.Delete(zkPath); err == nil { + _, err = r.client.RegisterTemp(root, node) + } + if err != nil { + logger.Errorf("Recreate the temp node failed, (root{%s}, node{%s}) = error{%v}", root, node, perrors.WithStack(err)) + } } return perrors.WithMessagef(err, "RegisterTempNode(root{%s}, node{%s})", root, node) } - logger.Debugf("create a zookeeper node:%s", zkPath) + logger.Debugf("Create a zookeeper node:%s", zkPath) return nil } func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListener, error) { - var ( - zkListener *RegistryConfigurationListener - ) - r.listenerLock.Lock() - if r.configListener.isClosed { - r.listenerLock.Unlock() - return nil, perrors.New("configListener already been closed") + var zkListener *RegistryConfigurationListener + dataListener := r.dataListener + dataListener.mutex.Lock() + defer dataListener.mutex.Unlock() + if r.dataListener.subscribed[conf.ServiceKey()] != nil { + + zkListener, _ := r.dataListener.subscribed[conf.ServiceKey()].(*RegistryConfigurationListener) + if zkListener != nil { + r.listenerLock.Lock() + defer r.listenerLock.Unlock() + if zkListener.isClosed { + return nil, perrors.New("configListener already been closed") + } else { + return zkListener, nil + } + } } - zkListener = r.configListener - r.listenerLock.Unlock() + + zkListener = NewRegistryConfigurationListener(r.client, r, conf) if r.listener == nil { r.cltLock.Lock() client := r.client @@ -216,10 +268,43 @@ func (r *zkRegistry) getListener(conf *common.URL) (*RegistryConfigurationListen } //Interested register to dataconfig. - r.dataListener.AddInterestedURL(conf) - for _, v := range strings.Split(conf.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), ",") { - go r.listener.ListenServiceEvent(fmt.Sprintf("/dubbo/%s/"+v, url.QueryEscape(conf.Service())), r.dataListener) + r.dataListener.SubscribeURL(conf, zkListener) + + go r.listener.ListenServiceEvent(conf, fmt.Sprintf("/dubbo/%s/"+constant.DEFAULT_CATEGORY, url.QueryEscape(conf.Service())), r.dataListener) + + 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 0d7623ca12a9b4e49f84ec988c796f2e913d537f..d915fc2ce10359f0dd1970daf019746ce066f511 100644 --- a/registry/zookeeper/registry_test.go +++ b/registry/zookeeper/registry_test.go @@ -41,10 +41,35 @@ func Test_Register(t *testing.T) { defer ts.Stop() err := reg.Register(url) children, _ := reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") - assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26category%3Dproviders%26cluster%3Dmock%26dubbo%3Ddubbo-provider-golang-1.3.0%26.*.serviceid%3Dsoa.mock%26.*provider", children) + assert.Regexp(t, ".*dubbo%3A%2F%2F127.0.0.1%3A20000%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26cluster%3Dmock%26.*.serviceid%3Dsoa.mock", children) 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/remoting/etcdv3/client.go b/remoting/etcdv3/client.go index ba3ea6e864923b1e70cc4a0d31ee98415807699c..731ef0d807a3287e0bcb557951b8640729beae46 100644 --- a/remoting/etcdv3/client.go +++ b/remoting/etcdv3/client.go @@ -42,6 +42,8 @@ const ( MaxFailTimes = 15 // RegistryETCDV3Client client name RegistryETCDV3Client = "etcd registry" + // metadataETCDV3Client client name + MetadataETCDV3Client = "etcd metadata" ) var ( @@ -107,7 +109,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) @@ -119,7 +121,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) @@ -149,7 +151,7 @@ type Client struct { Wait sync.WaitGroup } -func newClient(name string, endpoints []string, timeout time.Duration, heartbeat int) (*Client, error) { +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{ diff --git a/remoting/etcdv3/client_test.go b/remoting/etcdv3/client_test.go index 895cc2954adf93b5899d0ae5daedbd35834b7ef4..287d93e30dd125d743bf718dcdab574a4b5a4afc 100644 --- a/remoting/etcdv3/client_test.go +++ b/remoting/etcdv3/client_test.go @@ -29,11 +29,11 @@ import ( ) import ( + "github.com/coreos/etcd/embed" "github.com/coreos/etcd/mvcc/mvccpb" perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "go.etcd.io/etcd/embed" "google.golang.org/grpc/connectivity" ) @@ -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) diff --git a/remoting/kubernetes/client.go b/remoting/kubernetes/client.go index 0c9ffd2b914e6ad584023725867a3aaa4b641224..240257dbf55028a203bf9d419da0698fbfa9f8a3 100644 --- a/remoting/kubernetes/client.go +++ b/remoting/kubernetes/client.go @@ -19,442 +19,62 @@ package kubernetes import ( "context" - "encoding/base64" - "encoding/json" - "os" + "strconv" "sync" - "time" ) import ( perrors "github.com/pkg/errors" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/strategicpatch" - "k8s.io/apimachinery/pkg/watch" + v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" + "k8s.io/client-go/kubernetes/fake" ) import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" ) -const ( - // kubernetes inject the var - podNameKey = "HOSTNAME" - nameSpaceKey = "NAMESPACE" - // all pod annotation key - DubboIOAnnotationKey = "dubbo.io/annotation" - - DubboIOLabelKey = "dubbo.io/label" - DubboIOLabelValue = "dubbo.io-value" -) - -var ( - ErrDubboLabelAlreadyExist = perrors.New("dubbo label already exist") -) - type Client struct { - - // kubernetes connection config - cfg *rest.Config - - // the kubernetes interface - rawClient kubernetes.Interface - - // current pod config - currentPodName string - - ns string - - // current resource version - lastResourceVersion string - - // the memory watcherSet - watcherSet WatcherSet - - // protect the wg && currentPod lock sync.RWMutex - // current pod status - currentPod *v1.Pod - // protect the watchPods loop && watcher - wg sync.WaitGroup // manage the client lifecycle ctx context.Context cancel context.CancelFunc -} - -// load CurrentPodName -func getCurrentPodName() (string, error) { - - v := os.Getenv(podNameKey) - if len(v) == 0 { - return "", perrors.New("read value from env by key (HOSTNAME)") - } - return v, nil -} - -// load CurrentNameSpace -func getCurrentNameSpace() (string, error) { - - v := os.Getenv(nameSpaceKey) - if len(v) == 0 { - return "", perrors.New("read value from env by key (NAMESPACE)") - } - return v, nil -} - -// NewMockClient -// export for registry package test -func NewMockClient(namespace string, mockClientGenerator func() (kubernetes.Interface, error)) (*Client, error) { - return newMockClient(namespace, mockClientGenerator) -} - -// newMockClient -// new a client for test -func newMockClient(namespace string, mockClientGenerator func() (kubernetes.Interface, error)) (*Client, error) { - rawClient, err := mockClientGenerator() - if err != nil { - return nil, perrors.WithMessage(err, "call mock generator") - } - - currentPodName, err := getCurrentPodName() - if err != nil { - return nil, perrors.WithMessage(err, "get pod name") - } - - ctx, cancel := context.WithCancel(context.Background()) - - c := &Client{ - currentPodName: currentPodName, - ns: namespace, - rawClient: rawClient, - ctx: ctx, - watcherSet: newWatcherSet(ctx), - cancel: cancel, - } - - currentPod, err := c.initCurrentPod() - if err != nil { - return nil, perrors.WithMessage(err, "init current pod") - } - - // record current status - c.currentPod = currentPod - - // init the watcherSet by current pods - if err := c.initWatchSet(); err != nil { - return nil, perrors.WithMessage(err, "init watcherSet") - } - - c.lastResourceVersion = c.currentPod.GetResourceVersion() - - // start kubernetes watch loop - if err := c.watchPods(); err != nil { - return nil, perrors.WithMessage(err, "watch pods") - } - - logger.Infof("init kubernetes registry client success @namespace = %q @Podname = %q", namespace, c.currentPod.Name) - return c, nil + controller *dubboRegistryController } // newClient // new a client for registry -func newClient(namespace string) (*Client, error) { +func newClient(url common.URL) (*Client, error) { - cfg, err := rest.InClusterConfig() - if err != nil { - return nil, perrors.WithMessage(err, "get in-cluster config") - } + ctx, cancel := context.WithCancel(context.Background()) - rawClient, err := kubernetes.NewForConfig(cfg) + // read type + r, err := strconv.Atoi(url.GetParams().Get(constant.ROLE_KEY)) if err != nil { - return nil, perrors.WithMessage(err, "new kubernetes client by in cluster config") + return nil, perrors.WithMessage(err, "atoi role") } - - currentPodName, err := getCurrentPodName() + controller, err := newDubboRegistryController(ctx, common.RoleType(r), GetInClusterKubernetesClient) if err != nil { - return nil, perrors.WithMessage(err, "get pod name") + return nil, perrors.WithMessage(err, "new dubbo-registry controller") } - ctx, cancel := context.WithCancel(context.Background()) - c := &Client{ - currentPodName: currentPodName, - ns: namespace, - cfg: cfg, - rawClient: rawClient, - ctx: ctx, - watcherSet: newWatcherSet(ctx), - cancel: cancel, - } - - currentPod, err := c.initCurrentPod() - if err != nil { - return nil, perrors.WithMessage(err, "init current pod") - } - - // record current status - c.currentPod = currentPod - - // init the watcherSet by current pods - if err := c.initWatchSet(); err != nil { - return nil, perrors.WithMessage(err, "init watcherSet") + ctx: ctx, + cancel: cancel, + controller: controller, } - // start kubernetes watch loop - if err := c.watchPods(); err != nil { - return nil, perrors.WithMessage(err, "watch pods") + if r == common.CONSUMER { + // only consumer have to start informer factory + c.controller.startALLInformers() } - - logger.Infof("init kubernetes registry client success @namespace = %q @Podname = %q", namespace, c.currentPod.Name) return c, nil } -// initCurrentPod -// 1. get current pod -// 2. give the dubbo-label for this pod -func (c *Client) initCurrentPod() (*v1.Pod, error) { - - // read the current pod status - currentPod, err := c.rawClient.CoreV1().Pods(c.ns).Get(c.currentPodName, metav1.GetOptions{}) - if err != nil { - return nil, perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.currentPodName, c.ns) - } - - oldPod, newPod, err := c.assembleDUBBOLabel(currentPod) - if err != nil { - if err != ErrDubboLabelAlreadyExist { - return nil, perrors.WithMessage(err, "assemble dubbo label") - } - // current pod don't have label - } - - p, err := c.getPatch(oldPod, newPod) - if err != nil { - return nil, perrors.WithMessage(err, "get patch") - } - - currentPod, err = c.patchCurrentPod(p) - if err != nil { - return nil, perrors.WithMessage(err, "patch to current pod") - } - - return currentPod, nil -} - -// initWatchSet -// 1. get all with dubbo label pods -// 2. put every element to watcherSet -func (c *Client) initWatchSet() error { - - pods, err := c.rawClient.CoreV1().Pods(c.ns).List(metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector(DubboIOLabelKey, DubboIOLabelValue).String(), - }) - if err != nil { - return perrors.WithMessagef(err, "list pods in namespace (%s)", c.ns) - } - - // set resource version - c.lastResourceVersion = pods.GetResourceVersion() - - for _, pod := range pods.Items { - logger.Debugf("got the pod (name: %s), (label: %v), (annotations: %v)", pod.Name, pod.GetLabels(), pod.GetAnnotations()) - c.handleWatchedPodEvent(&pod, watch.Added) - } - - return nil -} - -// watchPods -// try to watch kubernetes pods -func (c *Client) watchPods() error { - - // try once - watcher, err := c.rawClient.CoreV1().Pods(c.ns).Watch(metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector(DubboIOLabelKey, DubboIOLabelValue).String(), - Watch: true, - ResourceVersion: c.lastResourceVersion, - }) - if err != nil { - return perrors.WithMessagef(err, "try to watch the namespace (%s) pods", c.ns) - } - - watcher.Stop() - - c.wg.Add(1) - // add wg, grace close the client - go c.watchPodsLoop() - return nil -} - -type resourceVersionGetter interface { - GetResourceVersion() string -} - -// watchPods -// try to notify -func (c *Client) watchPodsLoop() { - - defer func() { - // notify other goroutine, this loop over - c.wg.Done() - logger.Info("watchPodsLoop goroutine game over") - }() - - for { - onceWatch: - wc, err := c.rawClient.CoreV1().Pods(c.ns).Watch(metav1.ListOptions{ - LabelSelector: fields.OneTermEqualSelector(DubboIOLabelKey, DubboIOLabelValue).String(), - Watch: true, - ResourceVersion: c.lastResourceVersion, - }) - if err != nil { - logger.Warnf("watch the namespace (%s) pods: %v, retry after 2 seconds", c.ns, err) - time.Sleep(2 * time.Second) - continue - } - - logger.Infof("the old kubernetes client broken, collect the resource status from resource version (%s)", c.lastResourceVersion) - - for { - select { - // double check ctx - case <-c.ctx.Done(): - logger.Infof("the kubernetes client stopped, resultChan len %d", len(wc.ResultChan())) - return - - // get one element from result-chan - case event, ok := <-wc.ResultChan(): - if !ok { - wc.Stop() - logger.Info("kubernetes watch chan die, create new") - goto onceWatch - } - - if event.Type == watch.Error { - // watched a error event - logger.Warnf("kubernetes watch api report err (%#v)", event) - continue - } - - o, ok := event.Object.(resourceVersionGetter) - if !ok { - logger.Warnf("kubernetes response object not a versioned object, its real type %T", event.Object) - continue - } - - // record the last resource version avoid to sync all pod - c.lastResourceVersion = o.GetResourceVersion() - logger.Infof("kubernetes get the current resource version %v", c.lastResourceVersion) - - // check event object type - p, ok := event.Object.(*v1.Pod) - if !ok { - logger.Warnf("kubernetes response object not a Pod, its real type %T", event.Object) - continue - } - - logger.Debugf("kubernetes got pod %#v", p) - // handle the watched pod - go c.handleWatchedPodEvent(p, event.Type) - } - } - } -} - -// handleWatchedPodEvent -// handle watched pod event -func (c *Client) handleWatchedPodEvent(p *v1.Pod, eventType watch.EventType) { - - for ak, av := range p.GetAnnotations() { - - // not dubbo interest annotation - if ak != DubboIOAnnotationKey { - continue - } - - ol, err := c.unmarshalRecord(av) - if err != nil { - logger.Errorf("there a pod with dubbo annotation, but unmarshal dubbo value %v", err) - return - } - - for _, o := range ol { - - switch eventType { - case watch.Added: - // if pod is added, the record always be create - o.EventType = Create - case watch.Modified: - o.EventType = Update - case watch.Deleted: - o.EventType = Delete - default: - logger.Errorf("no valid kubernetes event-type (%s) ", eventType) - return - } - - logger.Debugf("prepare to put object (%#v) to kubernetes-watcherSet", o) - - if err := c.watcherSet.Put(o); err != nil { - logger.Errorf("put (%#v) to cache watcherSet: %v ", o, err) - return - } - - } - - } -} - -// unmarshalRecord -// unmarshal the kubernetes dubbo annotation value -func (c *Client) unmarshalRecord(record string) ([]*WatcherEvent, error) { - - if len(record) == 0 { - // []*WatcherEvent is nil. - return nil, nil - } - - rawMsg, err := base64.URLEncoding.DecodeString(record) - if err != nil { - return nil, perrors.WithMessagef(err, "decode record (%s)", record) - } - - var out []*WatcherEvent - if err := json.Unmarshal(rawMsg, &out); err != nil { - return nil, perrors.WithMessage(err, "decode json") - } - return out, nil -} - -// marshalRecord -// marshal the kubernetes dubbo annotation value -func (c *Client) marshalRecord(ol []*WatcherEvent) (string, error) { - - msg, err := json.Marshal(ol) - if err != nil { - return "", perrors.WithMessage(err, "json encode object list") - } - return base64.URLEncoding.EncodeToString(msg), nil -} - -// readCurrentPod -// read the current pod status from kubernetes api -func (c *Client) readCurrentPod() (*v1.Pod, error) { - - currentPod, err := c.rawClient.CoreV1().Pods(c.ns).Get(c.currentPodName, metav1.GetOptions{}) - if err != nil { - return nil, perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.currentPodName, c.ns) - } - return currentPod, nil -} - // Create // create k/v pair in watcher-set func (c *Client) Create(k, v string) error { @@ -464,132 +84,19 @@ func (c *Client) Create(k, v string) error { c.lock.Lock() defer c.lock.Unlock() - // 1. accord old pod && (k, v) assemble new pod dubbo annotion v - // 2. get patch data - // 3. PATCH the pod - currentPod, err := c.readCurrentPod() - if err != nil { - return perrors.WithMessage(err, "read current pod") - } - - oldPod, newPod, err := c.assembleDUBBOAnnotations(k, v, currentPod) - if err != nil { - return perrors.WithMessage(err, "assemble") - } - - patchBytes, err := c.getPatch(oldPod, newPod) - if err != nil { - return perrors.WithMessage(err, "get patch") - } - - updatedPod, err := c.patchCurrentPod(patchBytes) - if err != nil { - return perrors.WithMessage(err, "patch current pod") + if err := c.controller.addAnnotationForCurrentPod(k, v); err != nil { + return perrors.WithMessagef(err, "add annotation @key = %s @value = %s", k, v) } - c.currentPod = updatedPod logger.Debugf("put the @key = %s @value = %s success", k, v) - // not update the watcherSet, the watcherSet should be write by the watchPodsLoop return nil } -// patch current pod -// write new meta for current pod -func (c *Client) patchCurrentPod(patch []byte) (*v1.Pod, error) { - - updatedPod, err := c.rawClient.CoreV1().Pods(c.ns).Patch(c.currentPodName, types.StrategicMergePatchType, patch) - if err != nil { - return nil, perrors.WithMessage(err, "patch in kubernetes pod ") - } - return updatedPod, nil -} - -// assemble the dubbo kubernetes label -// every dubbo instance should be labeled spec {"dubbo.io/label":"dubbo.io/label-value"} label -func (c *Client) assembleDUBBOLabel(currentPod *v1.Pod) (*v1.Pod, *v1.Pod, error) { - - var ( - oldPod = &v1.Pod{} - newPod = &v1.Pod{} - ) - - oldPod.Labels = make(map[string]string, 8) - newPod.Labels = make(map[string]string, 8) - - if currentPod.GetLabels() != nil { - - if currentPod.GetLabels()[DubboIOLabelKey] == DubboIOLabelValue { - // already have label - return nil, nil, ErrDubboLabelAlreadyExist - } - } - - // copy current pod labels to oldPod && newPod - for k, v := range currentPod.GetLabels() { - oldPod.Labels[k] = v - newPod.Labels[k] = v - } - // assign new label for current pod - newPod.Labels[DubboIOLabelKey] = DubboIOLabelValue - return oldPod, newPod, nil -} - -// assemble the dubbo kubernetes annotations -// accord the current pod && (k,v) assemble the old-pod, new-pod -func (c *Client) assembleDUBBOAnnotations(k, v string, currentPod *v1.Pod) (oldPod *v1.Pod, newPod *v1.Pod, err error) { - - oldPod = &v1.Pod{} - newPod = &v1.Pod{} - oldPod.Annotations = make(map[string]string, 8) - newPod.Annotations = make(map[string]string, 8) - - for k, v := range currentPod.GetAnnotations() { - oldPod.Annotations[k] = v - newPod.Annotations[k] = v - } - - al, err := c.unmarshalRecord(oldPod.GetAnnotations()[DubboIOAnnotationKey]) - if err != nil { - err = perrors.WithMessage(err, "unmarshal record") - return - } - - newAnnotations, err := c.marshalRecord(append(al, &WatcherEvent{Key: k, Value: v})) - if err != nil { - err = perrors.WithMessage(err, "marshal record") - return - } - - newPod.Annotations[DubboIOAnnotationKey] = newAnnotations - return -} - -// getPatch -// get the kubernetes pod patch bytes -func (c *Client) getPatch(oldPod, newPod *v1.Pod) ([]byte, error) { - - oldData, err := json.Marshal(oldPod) - if err != nil { - return nil, perrors.WithMessage(err, "marshal old pod") - } - - newData, err := json.Marshal(newPod) - if err != nil { - return nil, perrors.WithMessage(err, "marshal newPod pod") - } - - patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Pod{}) - if err != nil { - return nil, perrors.WithMessage(err, "create two-way-merge-patch") - } - return patchBytes, nil -} - // GetChildren // get k children list from kubernetes-watcherSet func (c *Client) GetChildren(k string) ([]string, []string, error) { - objectList, err := c.watcherSet.Get(k, true) + objectList, err := c.controller.watcherSet.Get(k, true) if err != nil { return nil, nil, perrors.WithMessagef(err, "get children from watcherSet on (%s)", k) } @@ -609,7 +116,7 @@ func (c *Client) GetChildren(k string) ([]string, []string, error) { // watch on spec key func (c *Client) Watch(k string) (<-chan *WatcherEvent, <-chan struct{}, error) { - w, err := c.watcherSet.Watch(k, false) + w, err := c.controller.watcherSet.Watch(k, false) if err != nil { return nil, nil, perrors.WithMessagef(err, "watch on (%s)", k) } @@ -621,7 +128,7 @@ func (c *Client) Watch(k string) (<-chan *WatcherEvent, <-chan struct{}, error) // watch on spec prefix func (c *Client) WatchWithPrefix(prefix string) (<-chan *WatcherEvent, <-chan struct{}, error) { - w, err := c.watcherSet.Watch(prefix, true) + w, err := c.controller.watcherSet.Watch(prefix, true) if err != nil { return nil, nil, perrors.WithMessagef(err, "watch on prefix (%s)", prefix) } @@ -641,7 +148,7 @@ func (c *Client) Valid() bool { } c.lock.RLock() defer c.lock.RUnlock() - return c.rawClient != nil + return c.controller != nil } // Done @@ -665,7 +172,6 @@ func (c *Client) Close() { // the client ctx be canceled // will trigger the watcherSet watchers all stopped // so, just wait - c.wg.Wait() } // ValidateClient @@ -676,17 +182,36 @@ func ValidateClient(container clientFacade) error { // new Client if client == nil || client.Valid() { - ns, err := getCurrentNameSpace() - if err != nil { - return perrors.WithMessage(err, "get current namespace") - } - newClient, err := newClient(ns) + + newClient, err := newClient(container.GetUrl()) if err != nil { - logger.Warnf("new kubernetes client (namespace{%s}: %v)", ns, err) - return perrors.WithMessagef(err, "new kubernetes client (:%+v)", ns) + logger.Warnf("new kubernetes client: %v)", err) + return perrors.WithMessage(err, "new kubernetes client") } container.SetClient(newClient) } return nil } + +// NewMockClient +// export for registry package test +func NewMockClient(podList *v1.PodList) (*Client, error) { + + ctx, cancel := context.WithCancel(context.Background()) + controller, err := newDubboRegistryController(ctx, common.CONSUMER, func() (kubernetes.Interface, error) { + return fake.NewSimpleClientset(podList), nil + }) + if err != nil { + return nil, perrors.WithMessage(err, "new dubbo-registry controller") + } + + c := &Client{ + ctx: ctx, + cancel: cancel, + controller: controller, + } + + c.controller.startALLInformers() + return c, nil +} diff --git a/remoting/kubernetes/client_test.go b/remoting/kubernetes/client_test.go index 342285b345b5e45682fe792d35f2f910e7d86d9d..e116c48b1aa85b9b2331045dfbc42891aee1f2e1 100644 --- a/remoting/kubernetes/client_test.go +++ b/remoting/kubernetes/client_test.go @@ -20,20 +20,15 @@ package kubernetes import ( "encoding/json" "fmt" - "net/http" + _ "net/http/pprof" "os" - "runtime" "strings" "sync" "testing" - "time" ) import ( - "github.com/stretchr/testify/suite" v1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" ) // tests dataset @@ -64,220 +59,203 @@ var tests = []struct { // test dataset prefix const prefix = "name" -var clientPodJsonData = `{ +var clientPodListJsonData = `{ "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "annotations": { - "dubbo.io/annotation": "W3siayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzL2NvbnN1bWVyJTNBJTJGJTJGMTcyLjE3LjAuOCUyRlVzZXJQcm92aWRlciUzRmNhdGVnb3J5JTNEY29uc3VtZXJzJTI2ZHViYm8lM0RkdWJib2dvLWNvbnN1bWVyLTIuNi4wJTI2cHJvdG9jb2wlM0RkdWJibyIsInYiOiIifV0=" - }, - "creationTimestamp": "2020-03-13T03:38:57Z", - "labels": { - "dubbo.io/label": "dubbo.io-value" - }, - "name": "client", - "namespace": "default", - "resourceVersion": "2449700", - "selfLink": "/api/v1/namespaces/default/pods/client", - "uid": "3ec394f5-dcc6-49c3-8061-57b4b2b41344" - }, - "spec": { - "containers": [ - { - "env": [ + "items": [ + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "annotations": { + "dubbo.io/annotation": "W3siayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzL2R1YmJvJTNBJTJGJTJGMTcyLjE3LjAuNiUzQTIwMDAwJTJGVXNlclByb3ZpZGVyJTNGYWNjZXNzbG9nJTNEJTI2YW55aG9zdCUzRHRydWUlMjZhcHAudmVyc2lvbiUzRDAuMC4xJTI2YXBwbGljYXRpb24lM0RCRFRTZXJ2aWNlJTI2YXV0aCUzRCUyNmJlYW4ubmFtZSUzRFVzZXJQcm92aWRlciUyNmNsdXN0ZXIlM0RmYWlsb3ZlciUyNmVudmlyb25tZW50JTNEZGV2JTI2ZXhlY3V0ZS5saW1pdCUzRCUyNmV4ZWN1dGUubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNmdyb3VwJTNEJTI2aW50ZXJmYWNlJTNEY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyJTI2aXAlM0QxNzIuMTcuMC42JTI2bG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIubG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIucmV0cmllcyUzRDElMjZtZXRob2RzLkdldFVzZXIudHBzLmxpbWl0LmludGVydmFsJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5yYXRlJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNm1ldGhvZHMuR2V0VXNlci53ZWlnaHQlM0QwJTI2bW9kdWxlJTNEZHViYm9nbyUyQnVzZXItaW5mbyUyQnNlcnZlciUyNm5hbWUlM0RCRFRTZXJ2aWNlJTI2b3JnYW5pemF0aW9uJTNEaWt1cmVudG8uY29tJTI2b3duZXIlM0RaWCUyNnBhcmFtLnNpZ24lM0QlMjZwaWQlM0Q2JTI2cmVnaXN0cnkucm9sZSUzRDMlMjZyZWxlYXNlJTNEZHViYm8tZ29sYW5nLTEuMy4wJTI2cmV0cmllcyUzRCUyNnNlcnZpY2UuZmlsdGVyJTNEZWNobyUyNTJDdG9rZW4lMjUyQ2FjY2Vzc2xvZyUyNTJDdHBzJTI1MkNnZW5lcmljX3NlcnZpY2UlMjUyQ2V4ZWN1dGUlMjUyQ3BzaHV0ZG93biUyNnNpZGUlM0Rwcm92aWRlciUyNnRpbWVzdGFtcCUzRDE1OTExNTYxNTUlMjZ0cHMubGltaXQuaW50ZXJ2YWwlM0QlMjZ0cHMubGltaXQucmF0ZSUzRCUyNnRwcy5saW1pdC5yZWplY3RlZC5oYW5kbGVyJTNEJTI2dHBzLmxpbWl0LnN0cmF0ZWd5JTNEJTI2dHBzLmxpbWl0ZXIlM0QlMjZ2ZXJzaW9uJTNEJTI2d2FybXVwJTNEMTAwIiwidiI6IiJ9XQ==" + }, + "creationTimestamp": "2020-06-03T03:49:14Z", + "generateName": "server-84c864f5bc-", + "labels": { + "dubbo.io/label": "dubbo.io-value", + "pod-template-hash": "84c864f5bc", + "role": "server" + }, + "name": "server-84c864f5bc-r8qvz", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "server-84c864f5bc", + "uid": "fa376dbb-4f37-4705-8e80-727f592c19b3" + } + ], + "resourceVersion": "517460", + "selfLink": "/api/v1/namespaces/default/pods/server-84c864f5bc-r8qvz", + "uid": "f4fc811c-200c-4445-8d4f-532144957dcc" + }, + "spec": { + "containers": [ { - "name": "NAMESPACE", - "valueFrom": { - "fieldRef": { - "apiVersion": "v1", - "fieldPath": "metadata.namespace" + "env": [ + { + "name": "DUBBO_NAMESPACE", + "value": "default" + }, + { + "name": "NAMESPACE", + "valueFrom": { + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + } + } } - } + ], + "image": "192.168.240.101:5000/scott/go-server", + "imagePullPolicy": "Always", + "name": "server", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "dubbo-sa-token-5qbtb", + "readOnly": true + } + ] } ], - "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client", - "imagePullPolicy": "Always", - "name": "client", - "resources": {}, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "dubbo-sa", + "serviceAccountName": "dubbo-sa", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, { - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", - "name": "dubbo-sa-token-l2lzh", - "readOnly": true + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "dubbo-sa-token-5qbtb", + "secret": { + "defaultMode": 420, + "secretName": "dubbo-sa-token-5qbtb" + } } ] - } - ], - "dnsPolicy": "ClusterFirst", - "enableServiceLinks": true, - "nodeName": "minikube", - "priority": 0, - "restartPolicy": "Never", - "schedulerName": "default-scheduler", - "securityContext": {}, - "serviceAccount": "dubbo-sa", - "serviceAccountName": "dubbo-sa", - "terminationGracePeriodSeconds": 30, - "tolerations": [ - { - "effect": "NoExecute", - "key": "node.kubernetes.io/not-ready", - "operator": "Exists", - "tolerationSeconds": 300 - }, - { - "effect": "NoExecute", - "key": "node.kubernetes.io/unreachable", - "operator": "Exists", - "tolerationSeconds": 300 - } - ], - "volumes": [ - { - "name": "dubbo-sa-token-l2lzh", - "secret": { - "defaultMode": 420, - "secretName": "dubbo-sa-token-l2lzh" - } - } - ] - }, - "status": { - "conditions": [ - { - "lastProbeTime": null, - "lastTransitionTime": "2020-03-13T03:38:57Z", - "status": "True", - "type": "Initialized" }, - { - "lastProbeTime": null, - "lastTransitionTime": "2020-03-13T03:40:18Z", - "status": "True", - "type": "Ready" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2020-03-13T03:40:18Z", - "status": "True", - "type": "ContainersReady" - }, - { - "lastProbeTime": null, - "lastTransitionTime": "2020-03-13T03:38:57Z", - "status": "True", - "type": "PodScheduled" - } - ], - "containerStatuses": [ - { - "containerID": "docker://2870d6abc19ca7fe22ca635ebcfac5d48c6d5550a659bafd74fb48104f6dfe3c", - "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client:latest", - "imageID": "docker-pullable://registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client@sha256:1f075131f708a0d400339e81549d7c4d4ed917ab0b6bd38ef458dd06ad25a559", - "lastState": {}, - "name": "client", - "ready": true, - "restartCount": 0, - "state": { - "running": { - "startedAt": "2020-03-13T03:40:17Z" + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:14Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:15Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:15Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-06-03T03:49:14Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://b6421e05ce44f8a1c4fa6b72274980777c7c0f945516209f7c0558cd0cd65406", + "image": "192.168.240.101:5000/scott/go-server:latest", + "imageID": "docker-pullable://192.168.240.101:5000/scott/go-server@sha256:4eecf895054f0ff93d80db64992a561d10504e55582def6dcb6093a6d6d92461", + "lastState": {}, + "name": "server", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-06-03T03:49:15Z" + } + } + } + ], + "hostIP": "10.0.2.15", + "phase": "Running", + "podIP": "172.17.0.6", + "podIPs": [ + { + "ip": "172.17.0.6" } - } + ], + "qosClass": "BestEffort", + "startTime": "2020-06-03T03:49:14Z" } - ], - "hostIP": "10.0.2.15", - "phase": "Running", - "podIP": "172.17.0.8", - "qosClass": "BestEffort", - "startTime": "2020-03-13T03:38:57Z" + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" } } ` -type KubernetesClientTestSuite struct { - suite.Suite - - currentPod v1.Pod -} - -func (s *KubernetesClientTestSuite) initClient() *Client { - - t := s.T() - - client, err := newMockClient(s.currentPod.GetNamespace(), func() (kubernetes.Interface, error) { - - out := fake.NewSimpleClientset() - - // mock current pod - if _, err := out.CoreV1().Pods(s.currentPod.GetNamespace()).Create(&s.currentPod); err != nil { - t.Fatal(err) - } - return out, nil - }) - if err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - return client -} - -func (s *KubernetesClientTestSuite) SetupSuite() { - - runtime.GOMAXPROCS(1) - - t := s.T() +func getTestClient(t *testing.T) *Client { + pl := &v1.PodList{} // 1. install test data - if err := json.Unmarshal([]byte(clientPodJsonData), &s.currentPod); err != nil { + if err := json.Unmarshal([]byte(clientPodListJsonData), &pl); err != nil { t.Fatal(err) } + currentPod := pl.Items[0] - // 2. set downward-api inject env - if err := os.Setenv(podNameKey, s.currentPod.GetName()); err != nil { - t.Fatal(err) - } - if err := os.Setenv(nameSpaceKey, s.currentPod.GetNamespace()); err != nil { - t.Fatal(err) - } - - go http.ListenAndServe(":6061", nil) - -} - -func (s *KubernetesClientTestSuite) TestReadCurrentPodName() { - t := s.T() - - n, err := getCurrentPodName() - if err != nil { - t.Fatal(err) + env := map[string]string{ + nameSpaceKey: currentPod.GetNamespace(), + podNameKey: currentPod.GetName(), + needWatchedNameSpaceKey: "default", } - if n != s.currentPod.GetName() { - t.Fatalf("expect %s but got %s", s.currentPod.GetName(), n) + for k, v := range env { + if err := os.Setenv(k, v); err != nil { + t.Fatal(err) + } } -} -func (s *KubernetesClientTestSuite) TestReadCurrentNameSpace() { - t := s.T() - - ns, err := getCurrentNameSpace() + client, err := NewMockClient(pl) if err != nil { t.Fatal(err) } - if ns != s.currentPod.GetNamespace() { - t.Fatalf("expect %s but got %s", s.currentPod.GetNamespace(), ns) - } - + return client } -func (s *KubernetesClientTestSuite) TestClientValid() { - t := s.T() +func TestClientValid(t *testing.T) { - client := s.initClient() + client := getTestClient(t) defer client.Close() if client.Valid() != true { @@ -290,29 +268,24 @@ func (s *KubernetesClientTestSuite) TestClientValid() { } } -func (s *KubernetesClientTestSuite) TestClientDone() { - - t := s.T() +func TestClientDone(t *testing.T) { - client := s.initClient() + client := getTestClient(t) go func() { - time.Sleep(time.Second) client.Close() }() <-client.Done() if client.Valid() == true { - t.Fatal("client should be invalid then") + t.Fatal("client should be invalid") } } -func (s *KubernetesClientTestSuite) TestClientCreateKV() { +func TestClientCreateKV(t *testing.T) { - t := s.T() - - client := s.initClient() + client := getTestClient(t) defer client.Close() for _, tc := range tests { @@ -327,11 +300,9 @@ func (s *KubernetesClientTestSuite) TestClientCreateKV() { } } -func (s *KubernetesClientTestSuite) TestClientGetChildrenKVList() { - - t := s.T() +func TestClientGetChildrenKVList(t *testing.T) { - client := s.initClient() + client := getTestClient(t) defer client.Close() wg := sync.WaitGroup{} @@ -407,11 +378,9 @@ func (s *KubernetesClientTestSuite) TestClientGetChildrenKVList() { } -func (s *KubernetesClientTestSuite) TestClientWatchPrefix() { +func TestClientWatchPrefix(t *testing.T) { - t := s.T() - - client := s.initClient() + client := getTestClient(t) wg := sync.WaitGroup{} wg.Add(1) @@ -452,22 +421,9 @@ func (s *KubernetesClientTestSuite) TestClientWatchPrefix() { client.Close() } -func (s *KubernetesClientTestSuite) TestNewClient() { - - t := s.T() - - _, err := newClient(s.currentPod.GetNamespace()) - if err == nil { - t.Fatal("the out of cluster test should fail") - } - -} - -func (s *KubernetesClientTestSuite) TestClientWatch() { - - t := s.T() +func TestClientWatch(t *testing.T) { - client := s.initClient() + client := getTestClient(t) wg := sync.WaitGroup{} wg.Add(1) @@ -507,7 +463,3 @@ func (s *KubernetesClientTestSuite) TestClientWatch() { client.Close() } - -func TestKubernetesClient(t *testing.T) { - suite.Run(t, new(KubernetesClientTestSuite)) -} diff --git a/remoting/kubernetes/facade.go b/remoting/kubernetes/facade.go index dd15c918b45c353b8395e0b82aee82216f48cd0e..dc060bbb0eb673c6e380dfa3e9d5f7bacbd3fc0b 100644 --- a/remoting/kubernetes/facade.go +++ b/remoting/kubernetes/facade.go @@ -17,7 +17,10 @@ package kubernetes +import "github.com/apache/dubbo-go/common" + type clientFacade interface { Client() *Client SetClient(*Client) + common.Node } diff --git a/remoting/kubernetes/facade_test.go b/remoting/kubernetes/facade_test.go index 024264ffdee14c2ad3fb01a8a5279084c0f085d9..4323c0ec565d64f445f15c3e5c265451b2b87ce7 100644 --- a/remoting/kubernetes/facade_test.go +++ b/remoting/kubernetes/facade_test.go @@ -18,14 +18,18 @@ package kubernetes import ( + "strconv" "sync" + "testing" ) + import ( - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" ) type mockFacade struct { + *common.URL client *Client cltLock sync.Mutex done chan struct{} @@ -39,25 +43,31 @@ func (r *mockFacade) SetClient(client *Client) { r.client = client } -func (s *KubernetesClientTestSuite) Test_Facade() { +func (r *mockFacade) GetUrl() common.URL { + return *r.URL +} - t := s.T() +func (r *mockFacade) Destroy() { +} - mockClient, err := newMockClient(s.currentPod.GetNamespace(), func() (kubernetes.Interface, error) { +func (r *mockFacade) RestartCallBack() bool { + return true +} - out := fake.NewSimpleClientset() +func (r *mockFacade) IsAvailable() bool { + return true +} +func Test_Facade(t *testing.T) { - // mock current pod - if _, err := out.CoreV1().Pods(s.currentPod.GetNamespace()).Create(&s.currentPod); err != nil { - t.Fatal(err) - } - return out, nil - }) + regUrl, err := common.NewURL("registry://127.0.0.1:443", + common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER))) if err != nil { t.Fatal(err) } + mockClient := getTestClient(t) m := &mockFacade{ + URL: ®Url, client: mockClient, } diff --git a/remoting/kubernetes/listener.go b/remoting/kubernetes/listener.go index 4c198c66cc3e02006291a195af9d023ec5a02340..a5e7a544fadfc249426d34ce68081ab3d4b01bdb 100644 --- a/remoting/kubernetes/listener.go +++ b/remoting/kubernetes/listener.go @@ -81,8 +81,6 @@ func (l *EventListener) ListenServiceNodeEvent(key string, listener ...remoting. } } } - - return false } // return true mean the event type is DELETE diff --git a/remoting/kubernetes/listener_test.go b/remoting/kubernetes/listener_test.go index a9446782a5c336268c3d6418e5882031d1566ae8..1f398485b2f16defddf44ce1a08a7ecfd9760dd1 100644 --- a/remoting/kubernetes/listener_test.go +++ b/remoting/kubernetes/listener_test.go @@ -18,7 +18,7 @@ package kubernetes import ( - "time" + "testing" ) import ( @@ -67,9 +67,7 @@ func (m *mockDataListener) DataChange(eventType remoting.Event) bool { return true } -func (s *KubernetesClientTestSuite) TestListener() { - - t := s.T() +func TestListener(t *testing.T) { var tests = []struct { input struct { @@ -83,15 +81,13 @@ func (s *KubernetesClientTestSuite) TestListener() { }{k: "/dubbo", v: changedData}}, } - c := s.initClient() + c := getTestClient(t) defer c.Close() listener := NewEventListener(c) dataListener := &mockDataListener{client: c, changedData: changedData, rc: make(chan remoting.Event)} listener.ListenServiceEvent("/dubbo", dataListener) - // NOTICE: direct listen will lose create msg - time.Sleep(time.Second) for _, tc := range tests { k := tc.input.k diff --git a/remoting/kubernetes/registry_controller.go b/remoting/kubernetes/registry_controller.go new file mode 100644 index 0000000000000000000000000000000000000000..a9ca9e4d01ac19285d50a0b295b519538adf39cb --- /dev/null +++ b/remoting/kubernetes/registry_controller.go @@ -0,0 +1,604 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kubernetes + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" +) + +import ( + perrors "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/informers" + informerscorev1 "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" +) + +const ( + // kubernetes inject env var + podNameKey = "HOSTNAME" + nameSpaceKey = "NAMESPACE" + needWatchedNameSpaceKey = "DUBBO_NAMESPACE" + + // all pod annotation key + DubboIOAnnotationKey = "dubbo.io/annotation" + // all pod label key and value pair + DubboIOLabelKey = "dubbo.io/label" + DubboIOConsumerLabelValue = "dubbo.io.consumer" + DubboIOProviderLabelValue = "dubbo.io.provider" + + // kubernetes suggest resync + defaultResync = 5 * time.Minute +) + +var ( + ErrDubboLabelAlreadyExist = perrors.New("dubbo label already exist") +) + +// dubboRegistryController +// work like a kubernetes controller +type dubboRegistryController struct { + + // clone from client + // manage lifecycle + ctx context.Context + + role common.RoleType + + // protect patch current pod operation + lock sync.Mutex + + // current pod config + needWatchedNamespace map[string]struct{} + namespace string + name string + + watcherSet WatcherSet + + // kubernetes + kc kubernetes.Interface + listAndWatchStartResourceVersion uint64 + namespacedInformerFactory map[string]informers.SharedInformerFactory + namespacedPodInformers map[string]informerscorev1.PodInformer + queue workqueue.Interface //shared by namespaced informers +} + +func newDubboRegistryController( + ctx context.Context, + // different provider and consumer have behavior + roleType common.RoleType, + // used to inject mock kubernetes client + kcGetter func() (kubernetes.Interface, error), +) (*dubboRegistryController, error) { + + kc, err := kcGetter() + if err != nil { + return nil, perrors.WithMessage(err, "get kubernetes client") + } + + c := &dubboRegistryController{ + ctx: ctx, + role: roleType, + watcherSet: newWatcherSet(ctx), + needWatchedNamespace: make(map[string]struct{}), + namespacedInformerFactory: make(map[string]informers.SharedInformerFactory), + namespacedPodInformers: make(map[string]informerscorev1.PodInformer), + kc: kc, + } + + if err := c.readConfig(); err != nil { + return nil, perrors.WithMessage(err, "read config") + } + + if err := c.initCurrentPod(); err != nil { + return nil, perrors.WithMessage(err, "init current pod") + } + + if err := c.initWatchSet(); err != nil { + return nil, perrors.WithMessage(err, "init watch set") + } + + if err := c.initPodInformer(); err != nil { + return nil, perrors.WithMessage(err, "init pod informer") + } + + go c.run() + + return c, nil +} + +// GetInClusterKubernetesClient +// current pod running in kubernetes-cluster +func GetInClusterKubernetesClient() (kubernetes.Interface, error) { + + // read in-cluster config + cfg, err := rest.InClusterConfig() + if err != nil { + return nil, perrors.WithMessage(err, "get in-cluster config") + } + + return kubernetes.NewForConfig(cfg) +} + +// initWatchSet +// 1. get all with dubbo label pods +// 2. put every element to watcherSet +// 3. refresh watch book-mark +func (c *dubboRegistryController) initWatchSet() error { + + req, err := labels.NewRequirement(DubboIOLabelKey, selection.In, []string{DubboIOConsumerLabelValue, DubboIOProviderLabelValue}) + if err != nil { + return perrors.WithMessage(err, "new requirement") + } + + for ns := range c.needWatchedNamespace { + pods, err := c.kc.CoreV1().Pods(ns).List(metav1.ListOptions{ + LabelSelector: req.String(), + }) + if err != nil { + return perrors.WithMessagef(err, "list pods in namespace (%s)", ns) + } + for _, p := range pods.Items { + // set resource version + rv, err := strconv.ParseUint(p.GetResourceVersion(), 10, 0) + if err != nil { + return perrors.WithMessagef(err, "parse resource version %s", p.GetResourceVersion()) + } + if c.listAndWatchStartResourceVersion < rv { + c.listAndWatchStartResourceVersion = rv + } + c.handleWatchedPodEvent(&p, watch.Added) + } + } + return nil +} + +// read dubbo-registry controller config +// 1. current pod name +// 2. current pod working namespace +func (c *dubboRegistryController) readConfig() error { + + // read current pod name && namespace + c.name = os.Getenv(podNameKey) + if len(c.name) == 0 { + return perrors.New("read value from env by key (HOSTNAME)") + } + c.namespace = os.Getenv(nameSpaceKey) + if len(c.namespace) == 0 { + return perrors.New("read value from env by key (NAMESPACE)") + } + return nil +} + +func (c *dubboRegistryController) initNamespacedPodInformer(ns string) error { + + req, err := labels.NewRequirement(DubboIOLabelKey, selection.In, []string{DubboIOConsumerLabelValue, DubboIOProviderLabelValue}) + if err != nil { + return perrors.WithMessage(err, "new requirement") + } + + informersFactory := informers.NewSharedInformerFactoryWithOptions( + c.kc, + defaultResync, + informers.WithNamespace(ns), + informers.WithTweakListOptions(func(options *metav1.ListOptions) { + options.LabelSelector = req.String() + options.ResourceVersion = strconv.FormatUint(c.listAndWatchStartResourceVersion, 10) + }), + ) + podInformer := informersFactory.Core().V1().Pods() + + podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.addPod, + UpdateFunc: c.updatePod, + DeleteFunc: c.deletePod, + }) + + c.namespacedInformerFactory[ns] = informersFactory + c.namespacedPodInformers[ns] = podInformer + + return nil +} + +func (c *dubboRegistryController) initPodInformer() error { + + if c.role == common.PROVIDER { + return nil + } + + // read need watched namespaces list + needWatchedNameSpaceList := os.Getenv(needWatchedNameSpaceKey) + if len(needWatchedNameSpaceList) == 0 { + return perrors.New("read value from env by key (DUBBO_NAMESPACE)") + } + for _, ns := range strings.Split(needWatchedNameSpaceList, ",") { + c.needWatchedNamespace[ns] = struct{}{} + } + // current work namespace should be watched + c.needWatchedNamespace[c.namespace] = struct{}{} + + c.queue = workqueue.New() + + // init all watch needed pod-informer + for watchedNS := range c.needWatchedNamespace { + if err := c.initNamespacedPodInformer(watchedNS); err != nil { + return err + } + } + return nil +} + +type kubernetesEvent struct { + p *v1.Pod + t watch.EventType +} + +func (c *dubboRegistryController) addPod(obj interface{}) { + p, ok := obj.(*v1.Pod) + if !ok { + logger.Warnf("pod-informer got object %T not *v1.Pod", obj) + return + } + c.queue.Add(&kubernetesEvent{ + t: watch.Added, + p: p, + }) +} + +func (c *dubboRegistryController) updatePod(oldObj, newObj interface{}) { + op, ok := oldObj.(*v1.Pod) + if !ok { + logger.Warnf("pod-informer got object %T not *v1.Pod", oldObj) + return + } + np, ok := newObj.(*v1.Pod) + if !ok { + logger.Warnf("pod-informer got object %T not *v1.Pod", newObj) + return + } + if op.GetResourceVersion() == np.GetResourceVersion() { + return + } + c.queue.Add(&kubernetesEvent{ + p: np, + t: watch.Modified, + }) +} + +func (c *dubboRegistryController) deletePod(obj interface{}) { + p, ok := obj.(*v1.Pod) + if !ok { + logger.Warnf("pod-informer got object %T not *v1.Pod", obj) + return + } + c.queue.Add(&kubernetesEvent{ + p: p, + t: watch.Deleted, + }) +} + +func (c *dubboRegistryController) startALLInformers() { + logger.Debugf("starting namespaced informer-factory") + for _, factory := range c.namespacedInformerFactory { + go factory.Start(c.ctx.Done()) + } +} + +// run +// controller process every event in work-queue +func (c *dubboRegistryController) run() { + + if c.role == common.PROVIDER { + return + } + + defer logger.Warn("dubbo registry controller work stopped") + defer c.queue.ShutDown() + + for ns, podInformer := range c.namespacedPodInformers { + if !cache.WaitForCacheSync(c.ctx.Done(), podInformer.Informer().HasSynced) { + logger.Errorf("wait for cache sync finish @namespace %s fail", ns) + return + } + } + + logger.Infof("kubernetes registry-controller running @Namespace = %q @PodName = %q", c.namespace, c.name) + + // start work + go c.work() + // block wait context cancel + <-c.ctx.Done() +} + +func (c *dubboRegistryController) work() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem process work-queue elements +func (c *dubboRegistryController) processNextWorkItem() bool { + item, shutdown := c.queue.Get() + if shutdown { + return false + } + defer c.queue.Done(item) + o := item.(*kubernetesEvent) + c.handleWatchedPodEvent(o.p, o.t) + return true +} + +// handleWatchedPodEvent +// handle watched pod event +func (c *dubboRegistryController) handleWatchedPodEvent(p *v1.Pod, eventType watch.EventType) { + logger.Debugf("get @type = %s event from @pod = %s", eventType, p.GetName()) + + for ak, av := range p.GetAnnotations() { + // not dubbo interest annotation + if ak != DubboIOAnnotationKey { + continue + } + ol, err := c.unmarshalRecord(av) + if err != nil { + logger.Errorf("there a pod with dubbo annotation, but unmarshal dubbo value %v", err) + return + } + for _, o := range ol { + switch eventType { + case watch.Added: + // if pod is added, the record always be create + o.EventType = Create + case watch.Modified: + o.EventType = Update + case watch.Deleted: + o.EventType = Delete + default: + logger.Errorf("no valid kubernetes event-type (%s) ", eventType) + return + } + + logger.Debugf("putting @key=%s @value=%s to watcherSet", o.Key, o.Value) + if err := c.watcherSet.Put(o); err != nil { + logger.Errorf("put (%#v) to cache watcherSet: %v ", o, err) + return + } + } + } +} + +// unmarshalRecord +// unmarshal the kubernetes dubbo annotation value +func (c *dubboRegistryController) unmarshalRecord(record string) ([]*WatcherEvent, error) { + + if len(record) == 0 { + // []*WatcherEvent is nil. + return nil, nil + } + + rawMsg, err := base64.URLEncoding.DecodeString(record) + if err != nil { + return nil, perrors.WithMessagef(err, "decode record (%s)", record) + } + + var out []*WatcherEvent + if err := json.Unmarshal(rawMsg, &out); err != nil { + return nil, perrors.WithMessage(err, "decode json") + } + return out, nil +} + +// initCurrentPod +// 1. get current pod +// 2. give the dubbo-label for this pod +func (c *dubboRegistryController) initCurrentPod() error { + currentPod, err := c.readCurrentPod() + if err != nil { + return perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.name, c.namespace) + } + + oldPod, newPod, err := c.assembleDUBBOLabel(currentPod) + if err != nil { + if err == ErrDubboLabelAlreadyExist { + return nil + } + return perrors.WithMessage(err, "assemble dubbo label") + } + // current pod don't have label + p, err := c.getPatch(oldPod, newPod) + if err != nil { + return perrors.WithMessage(err, "get patch") + } + + currentPod, err = c.patchCurrentPod(p) + if err != nil { + return perrors.WithMessage(err, "patch to current pod") + } + + return nil +} + +// patch current pod +// write new meta for current pod +func (c *dubboRegistryController) patchCurrentPod(patch []byte) (*v1.Pod, error) { + updatedPod, err := c.kc.CoreV1().Pods(c.namespace).Patch(c.name, types.StrategicMergePatchType, patch) + if err != nil { + return nil, perrors.WithMessage(err, "patch in kubernetes pod ") + } + return updatedPod, nil +} + +// assemble the dubbo kubernetes label +// every dubbo instance should be labeled spec {"dubbo.io/label":"dubbo.io/label-value"} label +func (c *dubboRegistryController) assembleDUBBOLabel(p *v1.Pod) (*v1.Pod, *v1.Pod, error) { + var ( + oldPod = &v1.Pod{} + newPod = &v1.Pod{} + ) + oldPod.Labels = make(map[string]string, 8) + newPod.Labels = make(map[string]string, 8) + + if p.GetLabels() != nil { + if _, ok := p.GetLabels()[DubboIOLabelKey]; ok { + // already have label + return nil, nil, ErrDubboLabelAlreadyExist + } + } + + // copy current pod labels to oldPod && newPod + for k, v := range p.GetLabels() { + oldPod.Labels[k] = v + newPod.Labels[k] = v + } + + // assign new label for current pod + switch c.role { + case common.CONSUMER: + newPod.Labels[DubboIOLabelKey] = DubboIOConsumerLabelValue + case common.PROVIDER: + newPod.Labels[DubboIOLabelKey] = DubboIOProviderLabelValue + default: + return nil, nil, perrors.New(fmt.Sprintf("unknown role %s", c.role)) + } + return oldPod, newPod, nil +} + +// assemble the dubbo kubernetes annotations +// accord the current pod && (k,v) assemble the old-pod, new-pod +func (c *dubboRegistryController) assembleDUBBOAnnotations(k, v string, currentPod *v1.Pod) (oldPod *v1.Pod, newPod *v1.Pod, err error) { + + oldPod = &v1.Pod{} + newPod = &v1.Pod{} + oldPod.Annotations = make(map[string]string, 8) + newPod.Annotations = make(map[string]string, 8) + + for k, v := range currentPod.GetAnnotations() { + oldPod.Annotations[k] = v + newPod.Annotations[k] = v + } + + al, err := c.unmarshalRecord(oldPod.GetAnnotations()[DubboIOAnnotationKey]) + if err != nil { + err = perrors.WithMessage(err, "unmarshal record") + return + } + + newAnnotations, err := c.marshalRecord(append(al, &WatcherEvent{Key: k, Value: v})) + if err != nil { + err = perrors.WithMessage(err, "marshal record") + return + } + + newPod.Annotations[DubboIOAnnotationKey] = newAnnotations + return +} + +// getPatch +// get the kubernetes pod patch bytes +func (c *dubboRegistryController) getPatch(oldPod, newPod *v1.Pod) ([]byte, error) { + oldData, err := json.Marshal(oldPod) + if err != nil { + return nil, perrors.WithMessage(err, "marshal old pod") + } + + newData, err := json.Marshal(newPod) + if err != nil { + return nil, perrors.WithMessage(err, "marshal newPod pod") + } + + patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Pod{}) + if err != nil { + return nil, perrors.WithMessage(err, "create two-way-merge-patch") + } + return patchBytes, nil +} + +// marshalRecord +// marshal the kubernetes dubbo annotation value +func (c *dubboRegistryController) marshalRecord(ol []*WatcherEvent) (string, error) { + msg, err := json.Marshal(ol) + if err != nil { + return "", perrors.WithMessage(err, "json encode object list") + } + return base64.URLEncoding.EncodeToString(msg), nil +} + +// read from kubernetes-env current pod status +func (c *dubboRegistryController) readCurrentPod() (*v1.Pod, error) { + currentPod, err := c.kc.CoreV1().Pods(c.namespace).Get(c.name, metav1.GetOptions{}) + if err != nil { + return nil, perrors.WithMessagef(err, "get current (%s) pod in namespace (%s)", c.name, c.namespace) + } + return currentPod, nil +} + +// add annotation for current pod +func (c *dubboRegistryController) addAnnotationForCurrentPod(k string, v string) error { + + c.lock.Lock() + defer c.lock.Unlock() + + // 1. accord old pod && (k, v) assemble new pod dubbo annotion v + // 2. get patch data + // 3. PATCH the pod + currentPod, err := c.readCurrentPod() + if err != nil { + return perrors.WithMessage(err, "read current pod") + } + + oldPod, newPod, err := c.assembleDUBBOAnnotations(k, v, currentPod) + if err != nil { + return perrors.WithMessage(err, "assemble") + } + + patchBytes, err := c.getPatch(oldPod, newPod) + if err != nil { + return perrors.WithMessage(err, "get patch") + } + + _, err = c.patchCurrentPod(patchBytes) + if err != nil { + return perrors.WithMessage(err, "patch current pod") + } + + return c.watcherSet.Put(&WatcherEvent{ + Key: k, + Value: v, + EventType: Create, + }) +} diff --git a/remoting/kubernetes/watch.go b/remoting/kubernetes/watch.go index c99a3ebcc041f2fed0160f1f286e72937d2c9aee..3293ff1d923adb994ee9a7e9b9e79b6abb621195 100644 --- a/remoting/kubernetes/watch.go +++ b/remoting/kubernetes/watch.go @@ -71,6 +71,7 @@ type WatcherEvent struct { } // Watchable WatcherSet +// thread-safe type WatcherSet interface { // put the watch event to the watch set @@ -149,7 +150,7 @@ func (s *watcherSetImpl) Done() <-chan struct{} { // put the watch event to watcher-set func (s *watcherSetImpl) Put(watcherEvent *WatcherEvent) error { - sendMsg := func(object *WatcherEvent, w *watcher) { + blockSendMsg := func(object *WatcherEvent, w *watcher) { select { case <-w.done(): @@ -167,40 +168,40 @@ func (s *watcherSetImpl) Put(watcherEvent *WatcherEvent) error { } // put to watcher-set - if watcherEvent.EventType == Delete { + switch watcherEvent.EventType { + case Delete: + // delete from store delete(s.cache, watcherEvent.Key) - } else { - - old, ok := s.cache[watcherEvent.Key] - if ok { - if old.Value == watcherEvent.Value { - // already have this k/v pair - return nil - } + case Update, Create: + o, ok := s.cache[watcherEvent.Key] + if !ok { + // pod update, but create new k/v pair + watcherEvent.EventType = Create + s.cache[watcherEvent.Key] = watcherEvent + break } - - // refresh the watcherEvent + // k/v pair already latest + if o.Value == watcherEvent.Value { + return nil + } + // update to latest status s.cache[watcherEvent.Key] = watcherEvent } // notify watcher for _, w := range s.watchers { - - w := w - if !strings.Contains(watcherEvent.Key, w.interested.key) { // this watcher no interest in this element continue } - if !w.interested.prefix { if watcherEvent.Key == w.interested.key { - go sendMsg(watcherEvent, w) + blockSendMsg(watcherEvent, w) } // not interest continue } - go sendMsg(watcherEvent, w) + blockSendMsg(watcherEvent, w) } return nil } diff --git a/remoting/listener.go b/remoting/listener.go index 3713ba0ccf9d98d4470741785a9490e657cf051c..f7a3a2bd1662734919e093e1bd769223cd53447b 100644 --- a/remoting/listener.go +++ b/remoting/listener.go @@ -30,7 +30,7 @@ type DataListener interface { // event type ////////////////////////////////////////// -// EventType ... +// SourceObjectEventType ... type EventType int const ( diff --git a/remoting/nacos/builder.go b/remoting/nacos/builder.go new file mode 100644 index 0000000000000000000000000000000000000000..545a1e268cabe5cef829ff1cf44ef40b1161d590 --- /dev/null +++ b/remoting/nacos/builder.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 nacos + +import ( + "net" + "strconv" + "strings" + "time" + + "github.com/apache/dubbo-go/config" +) + +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" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" +) + +func NewNacosNamingClient(url *common.URL) (naming_client.INamingClient, error) { + nacosConfig, err := getNacosConfig(url) + if err != nil { + return nil, err + } + return clients.CreateNamingClient(nacosConfig) +} + +func NewNacosConfigClient(url *common.URL) (config_client.IConfigClient, error) { + nacosConfig, err := getNacosConfig(url) + if err != nil { + return nil, err + } + return clients.CreateConfigClient(nacosConfig) +} + +// getNacosConfig will return the nacos config +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, err := strconv.Atoi(portStr) + if err != nil { + return configMap, perrors.WithMessage(err, "the port string is invalid. "+portStr) + } + serverConfigs = append(serverConfigs, nacosConstant.ServerConfig{ + IpAddr: ip, + Port: uint64(port), + }) + } + configMap["serverConfigs"] = serverConfigs + + timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + if err != nil { + return nil, err + } + + timeoutMs := uint64(timeout.Nanoseconds() / constant.MsToNanoRate) + + configMap["clientConfig"] = nacosConstant.ClientConfig{ + TimeoutMs: timeoutMs, + ListenInterval: 2 * timeoutMs, + CacheDir: url.GetParam(constant.NACOS_CACHE_DIR_KEY, ""), + LogDir: url.GetParam(constant.NACOS_LOG_DIR_KEY, ""), + Endpoint: url.GetParam(constant.NACOS_ENDPOINT, ""), + NotLoadCacheAtStart: true, + } + + return configMap, nil +} + +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.GetParam(constant.NACOS_ENDPOINT, "") + clientConfig.NotLoadCacheAtStart = true + configMap["clientConfig"] = clientConfig + + return clients.CreateNamingClient(configMap) +} diff --git a/remoting/nacos/builder_test.go b/remoting/nacos/builder_test.go new file mode 100644 index 0000000000000000000000000000000000000000..61d13ef26f9f1d17173bbeb11468f9babdade2f5 --- /dev/null +++ b/remoting/nacos/builder_test.go @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package nacos + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/config" +) + +func TestNewNacosClient(t *testing.T) { + rc := &config.RemoteConfig{} + client, err := NewNacosClient(rc) + + // address is nil + assert.NotNil(t, err) + + rc.Address = "console.nacos.io:80:123" + client, err = NewNacosClient(rc) + // invalid address + assert.NotNil(t, err) + + 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 7dac6146fa2aab88771ef5b7cb0205736e6478a9..f4aea5903d534a008e3c94bb04f127a67988132b 100644 --- a/remoting/zookeeper/client.go +++ b/remoting/zookeeper/client.go @@ -51,7 +51,7 @@ var ( type ZookeeperClient struct { name string ZkAddrs []string - sync.Mutex // for conn + sync.RWMutex // for conn Conn *zk.Conn Timeout time.Duration exit chan struct{} @@ -87,8 +87,6 @@ func StateToString(state zk.State) string { default: return state.String() } - - return "zookeeper unknown state" } // Options ... @@ -111,14 +109,12 @@ func WithZkName(name string) Option { // ValidateZookeeperClient ... func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error { - var ( - err error - ) - opions := &Options{} + var err error + options := &Options{} for _, opt := range opts { - opt(opions) + opt(options) } - + connected := false err = nil lock := container.ZkClientLock() @@ -129,20 +125,22 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error { if container.ZkClient() == nil { //in dubbo ,every registry only connect one node ,so this is []string{r.Address} - timeout, err := time.ParseDuration(url.GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) + var timeout time.Duration + 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(opions.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}", - opions.zkName, url.Location, timeout.String(), err) + options.zkName, url.Location, timeout.String(), err) return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.Location) } container.SetZkClient(newClient) + connected = true } if container.ZkClient().Conn == nil { @@ -150,10 +148,16 @@ func ValidateZookeeperClient(container zkClientFacade, opts ...Option) error { container.ZkClient().Conn, event, err = zk.Connect(container.ZkClient().ZkAddrs, container.ZkClient().Timeout) if err == nil { container.ZkClient().Wait.Add(1) + connected = true go container.ZkClient().HandleZkEvent(event) } } + 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 + } + return perrors.WithMessagef(err, "newZookeeperClient(address:%+v)", url.PrimitiveURL) } @@ -271,7 +275,7 @@ LOOP: break LOOP 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.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", @@ -281,7 +285,7 @@ LOOP: } } } - z.Unlock() + z.RUnlock() case (int)(zk.StateConnecting), (int)(zk.StateConnected), (int)(zk.StateHasSession): if state == (int)(zk.StateHasSession) { continue @@ -364,11 +368,11 @@ func (z *ZookeeperClient) ZkConnValid() bool { } valid := true - z.Lock() + z.RLock() if z.Conn == nil { valid = false } - z.Unlock() + z.RUnlock() return valid } @@ -386,6 +390,7 @@ 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()) conn.Close() } @@ -408,15 +413,15 @@ 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 { @@ -438,9 +443,7 @@ func (z *ZookeeperClient) Delete(basePath string) error { ) err = errNilZkClientConn - z.Lock() - conn := z.Conn - z.Unlock() + conn := z.getConn() if conn != nil { err = conn.Delete(basePath, -1) } @@ -460,9 +463,7 @@ func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, er 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)) } @@ -470,7 +471,7 @@ func (z *ZookeeperClient) RegisterTemp(basePath string, node string) (string, er //if err != nil && err != zk.ErrNodeExists { if err != nil { logger.Warnf("conn.Create(\"%s\", zk.FlagEphemeral) = error(%v)\n", zkPath, perrors.WithStack(err)) - return "", perrors.WithStack(err) + return zkPath, perrors.WithStack(err) } logger.Debugf("zkClient{%s} create a temp zookeeper node:%s\n", z.name, tmpPath) @@ -485,9 +486,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)+"/", @@ -518,9 +517,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) } @@ -554,9 +551,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) } @@ -587,9 +582,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) } @@ -610,3 +603,10 @@ 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) } + +// 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 cb41eb326be95470e39694fc5df233fdf073b905..0f6899568ad4744dc58022c41e22db6f901ad5de 100644 --- a/remoting/zookeeper/client_test.go +++ b/remoting/zookeeper/client_test.go @@ -106,7 +106,6 @@ func TestCreateDelete(t *testing.T) { 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) { diff --git a/remoting/zookeeper/facade.go b/remoting/zookeeper/facade.go index 055db4f716a914354d1bada653fbc0a850b615b5..4e3945388ff402f60a02150615a8914f9cba2435 100644 --- a/remoting/zookeeper/facade.go +++ b/remoting/zookeeper/facade.go @@ -48,11 +48,11 @@ func HandleClientRestart(r zkClientFacade) { failTimes int ) - defer r.WaitGroup().Done() LOOP: for { select { case <-r.Done(): + r.WaitGroup().Done() // dec the wg when registry is closed logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") break LOOP // re-register all services @@ -63,12 +63,14 @@ LOOP: zkAddress := r.ZkClient().ZkAddrs r.SetZkClient(nil) r.ZkClientLock().Unlock() + r.WaitGroup().Done() // dec the wg when zk client is closed // Connect zk until success. failTimes = 0 for { select { case <-r.Done(): + r.WaitGroup().Done() // dec the wg when registry is closed logger.Warnf("(ZkProviderRegistry)reconnectZkRegistry goroutine exit now...") break LOOP case <-getty.GetTimeWheel().After(timeSecondDuration(failTimes * ConnDelay)): // Prevent crazy reconnection zk. diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go index a41f6cd3230700332519ce1c2d3489bfcc4b6ef0..01d46da6cc1abae90210a323d32ac84bad80249b 100644 --- a/remoting/zookeeper/facade_test.go +++ b/remoting/zookeeper/facade_test.go @@ -38,6 +38,16 @@ type mockFacade struct { done chan struct{} } +func newMockFacade(client *ZookeeperClient, url *common.URL) zkClientFacade { + mock := &mockFacade{ + client: client, + URL: url, + } + + mock.wg.Add(1) + return mock +} + func (r *mockFacade) ZkClient() *ZookeeperClient { return r.client } @@ -80,7 +90,7 @@ func Test_Facade(t *testing.T) { assert.NoError(t, err) defer ts.Stop() url, _ := common.NewURL("mock://127.0.0.1") - mock := &mockFacade{client: z, URL: &url} + mock := newMockFacade(z, &url) go HandleClientRestart(mock) states := []zk.State{zk.StateConnecting, zk.StateConnected, zk.StateHasSession} verifyEventStateOrder(t, event, states, "event channel") diff --git a/remoting/zookeeper/listener.go b/remoting/zookeeper/listener.go index eaf259f4417201c95172e95d7a87476575e004d5..f9d57ba5c2276181bb551e8b8499d850b87d041a 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -31,6 +31,7 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/remoting" @@ -95,8 +96,6 @@ func (l *ZkEventListener) ListenServiceNodeEvent(zkPath string, listener ...remo return false } } - - return false } func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, listener remoting.DataListener) { @@ -173,7 +172,7 @@ func (l *ZkEventListener) handleZkNodeEvent(zkPath string, children []string, li } } -func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataListener) { +func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listener remoting.DataListener) { defer l.wg.Done() var ( @@ -224,7 +223,16 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi } failTimes = 0 for _, c := range children { - // listen l service node + + // 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 { + continue + } + } + + //listen l service node dubboPath := path.Join(zkPath, c) //Save the path to avoid listen repeatedly @@ -232,7 +240,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi _, ok := l.pathMap[dubboPath] l.pathMapLock.Unlock() if ok { - logger.Warnf("@zkPath %s has already been listened.", zkPath) + logger.Warnf("@zkPath %s has already been listened.", dubboPath) continue } @@ -263,7 +271,7 @@ func (l *ZkEventListener) listenDirEvent(zkPath string, listener remoting.DataLi strings.LastIndex(zkPath, constant.CONSUMER_CATEGORY) == -1 { l.wg.Add(1) go func(zkPath string, listener remoting.DataListener) { - l.listenDirEvent(zkPath, listener) + l.listenDirEvent(conf, zkPath, listener) logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath) }(dubboPath, listener) } @@ -291,11 +299,11 @@ func timeSecondDuration(sec int) time.Duration { // registry.go:Listen -> listenServiceEvent -> listenDirEvent -> ListenServiceNodeEvent // | // --------> ListenServiceNodeEvent -func (l *ZkEventListener) ListenServiceEvent(zkPath string, listener remoting.DataListener) { +func (l *ZkEventListener) ListenServiceEvent(conf *common.URL, zkPath string, listener remoting.DataListener) { logger.Infof("listen dubbo path{%s}", zkPath) l.wg.Add(1) go func(zkPath string, listener remoting.DataListener) { - l.listenDirEvent(zkPath, listener) + l.listenDirEvent(conf, zkPath, listener) logger.Warnf("listenDirEvent(zkPath{%s}) goroutine exit now", zkPath) }(zkPath, listener) } @@ -304,7 +312,8 @@ func (l *ZkEventListener) valid() bool { return l.client.ZkConnValid() } -// Close ... +// Close will let client listen exit func (l *ZkEventListener) Close() { + close(l.client.exit) l.wg.Wait() } diff --git a/remoting/zookeeper/listener_test.go b/remoting/zookeeper/listener_test.go index 7301cd52c392b6950b3a49f78e8124eae532b083..ba7d6ba81b6af97dc5ad3788e8399d08cbe5b2bb 100644 --- a/remoting/zookeeper/listener_test.go +++ b/remoting/zookeeper/listener_test.go @@ -97,7 +97,7 @@ func TestListener(t *testing.T) { go client.HandleZkEvent(event) listener := NewZkEventListener(client) dataListener := &mockDataListener{client: client, changedData: changedData, wait: &wait} - listener.ListenServiceEvent("/dubbo", dataListener) + listener.ListenServiceEvent(nil, "/dubbo", dataListener) time.Sleep(1 * time.Second) _, err := client.Conn.Set("/dubbo/dubbo.properties", []byte(changedData), 1) assert.NoError(t, err) diff --git a/test/integrate/dubbo/go-client/Dockerfile b/test/integrate/dubbo/go-client/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d48df36dc72d7e75f8c2c8c91d5acbb01e39757d --- /dev/null +++ b/test/integrate/dubbo/go-client/Dockerfile @@ -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. +# + +FROM golang + +WORKDIR /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client + +ENV CONF_CONSUMER_FILE_PATH "client.yml" +ENV APP_LOG_CONF_FILE "log.yml" + +ARG PR_ORIGIN_REPO +ARG PR_ORIGIN_COMMITID + +ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-client + +# update dubbo-go to current commit id +RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' +RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop + +RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-client + +CMD go-client \ No newline at end of file diff --git a/test/integrate/dubbo/go-client/client.go b/test/integrate/dubbo/go-client/client.go new file mode 100644 index 0000000000000000000000000000000000000000..c075ec22c3991aaea1b24ec4f59b3ab7e58520b4 --- /dev/null +++ b/test/integrate/dubbo/go-client/client.go @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + "fmt" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + _ "github.com/apache/dubbo-go/event/proxy/proxy_factory" + "github.com/apache/dubbo-go/config" + _ "github.com/apache/dubbo-go/protocol/dubbo" + _ "github.com/apache/dubbo-go/registry/protocol" + + _ "github.com/apache/dubbo-go/filter/filter_impl" + + _ "github.com/apache/dubbo-go/cluster/cluster_impl" + _ "github.com/apache/dubbo-go/cluster/loadbalance" + _ "github.com/apache/dubbo-go/registry/zookeeper" +) + +var ( + survivalTimeout int = 10e9 +) + +func println(format string, args ...interface{}) { + fmt.Printf("\033[32;40m"+format+"\033[0m\n", args...) +} + +// they are necessary: +// export CONF_CONSUMER_FILE_PATH="xxx" +// export APP_LOG_CONF_FILE="xxx" +func main() { + hessian.RegisterPOJO(&User{}) + config.Load() + time.Sleep(3e9) + + println("\n\n\nstart to test dubbo") + + go func() { + select { + case <-time.After(time.Minute): + panic("provider not start after client already running 1min") + } + }() + user := &User{} + err := userProvider.GetUser(context.TODO(), []interface{}{"A001"}, user) + if err != nil { + panic(err) + } + println("response result: %v\n", user) +} diff --git a/test/integrate/dubbo/go-client/client.yml b/test/integrate/dubbo/go-client/client.yml new file mode 100644 index 0000000000000000000000000000000000000000..df44a13da38a14fa4a81b6189aa05708cf6f5599 --- /dev/null +++ b/test/integrate/dubbo/go-client/client.yml @@ -0,0 +1,61 @@ +# dubbo client yaml configure file + + +check: true +# client +request_timeout : "3s" +# connect timeout +connect_timeout : "3s" + +# application config +application: + organization : "ikurento.com" + name : "BDTService" + module : "dubbogo user-info client" + version : "0.0.1" + owner : "ZX" + environment : "dev" + +registries : + "demoZk": + protocol: "zookeeper" + timeout : "3s" + address: "127.0.0.1:2181" + username: "" + password: "" + + +references: + "UserProvider": + # 鍙互鎸囧畾澶氫釜registry锛屼娇鐢ㄩ€楀彿闅斿紑;涓嶆寚瀹氶粯璁ゅ悜鎵€鏈夋敞鍐屼腑蹇冩敞鍐� + registry: "demoZk" + protocol : "dubbo" + interface : "com.ikurento.user.UserProvider" + cluster: "failover" + methods : + - name: "GetUser" + retries: 3 + + +protocol_conf: + dubbo: + reconnect_interval: 0 + connection_number: 2 + heartbeat_period: "5s" + session_timeout: "20s" + pool_size: 64 + pool_ttl: 600 + getty_session_param: + compress_encoding: false + tcp_no_delay: true + tcp_keep_alive: true + keep_alive_period: "120s" + tcp_r_buf_size: 262144 + tcp_w_buf_size: 65536 + pkg_rq_size: 1024 + pkg_wq_size: 512 + tcp_read_timeout: "1s" + tcp_write_timeout: "5s" + wait_timeout: "1s" + max_msg_len: 1024000 + session_name: "client" diff --git a/test/integrate/dubbo/go-client/go.mod b/test/integrate/dubbo/go-client/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..4708eb1f0f48c10acc254880ecb6dad3a03529f2 --- /dev/null +++ b/test/integrate/dubbo/go-client/go.mod @@ -0,0 +1,3 @@ +module github.com/apache/dubbo-go/test/integrate/dubbo/go-client + +go 1.13 diff --git a/test/integrate/dubbo/go-client/log.yml b/test/integrate/dubbo/go-client/log.yml new file mode 100644 index 0000000000000000000000000000000000000000..59fa4279ad85272c4c49d532beaf23b74d00f58a --- /dev/null +++ b/test/integrate/dubbo/go-client/log.yml @@ -0,0 +1,28 @@ + +level: "debug" +development: true +disableCaller: false +disableStacktrace: false +sampling: +encoding: "console" + +# encoder +encoderConfig: + messageKey: "message" + levelKey: "level" + timeKey: "time" + nameKey: "logger" + callerKey: "caller" + stacktraceKey: "stacktrace" + lineEnding: "" + levelEncoder: "capitalColor" + timeEncoder: "iso8601" + durationEncoder: "seconds" + callerEncoder: "short" + nameEncoder: "" + +outputPaths: + - "stderr" +errorOutputPaths: + - "stderr" +initialFields: diff --git a/test/integrate/dubbo/go-client/user.go b/test/integrate/dubbo/go-client/user.go new file mode 100644 index 0000000000000000000000000000000000000000..ff4486f07975ebbb0064a8c83b71c952e6311dbb --- /dev/null +++ b/test/integrate/dubbo/go-client/user.go @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "context" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/apache/dubbo-go/config" +) + +var userProvider = new(UserProvider) + +func init() { + config.SetConsumerService(userProvider) + hessian.RegisterPOJO(&User{}) +} + +type User struct { + Id string + Name string + Age int32 + Time time.Time +} + +type UserProvider struct { + GetUser func(ctx context.Context, req []interface{}, rsp *User) error +} + +func (u *UserProvider) Reference() string { + return "UserProvider" +} + +func (User) JavaClassName() string { + return "com.ikurento.user.User" +} diff --git a/test/integrate/dubbo/go-client/version.go b/test/integrate/dubbo/go-client/version.go new file mode 100644 index 0000000000000000000000000000000000000000..c6138584f1ddeab3a4927774f44f9e78a8f08da7 --- /dev/null +++ b/test/integrate/dubbo/go-client/version.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +var ( + Version = "2.6.0" +) diff --git a/test/integrate/dubbo/go-server/Dockerfile b/test/integrate/dubbo/go-server/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..c2f2d63462d94df7624ac100023e8b8c24e23e11 --- /dev/null +++ b/test/integrate/dubbo/go-server/Dockerfile @@ -0,0 +1,35 @@ +# +#Licensed to the Apache Software Foundation (ASF) under one or more +#contributor license agreements. See the NOTICE file distributed with +#this work for additional information regarding copyright ownership. +#The ASF licenses this file to You under the Apache License, Version 2.0 +#(the "License"); you may not use this file except in compliance with +#the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. +# + +FROM golang + +WORKDIR /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server + +ENV CONF_PROVIDER_FILE_PATH "server.yml" +ENV APP_LOG_CONF_FILE "log.yml" + +ARG PR_ORIGIN_REPO +ARG PR_ORIGIN_COMMITID + +ADD . /go/src/github.com/apache/dubbo-go/test/integrate/dubbo/go-server +# update dubbo-go to current commit id +RUN test ${PR_ORIGIN_REPO} && echo "github.com/apache/dubbo-go will be replace to github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID}" || echo 'go get github.com/apache/dubbo-go@develop' +RUN test ${PR_ORIGIN_REPO} && go mod edit -replace=github.com/apache/dubbo-go=github.com/${PR_ORIGIN_REPO}@${PR_ORIGIN_COMMITID} || go get -u github.com/apache/dubbo-go@develop + +RUN go install github.com/apache/dubbo-go/test/integrate/dubbo/go-server + +CMD go-server diff --git a/test/integrate/dubbo/go-server/go.mod b/test/integrate/dubbo/go-server/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..9e1162327de374fb131c2a0b89d1be3baa578a1b --- /dev/null +++ b/test/integrate/dubbo/go-server/go.mod @@ -0,0 +1,3 @@ +module github.com/apache/dubbo-go/test/integrate/dubbo/go-server + +go 1.13 diff --git a/test/integrate/dubbo/go-server/log.yml b/test/integrate/dubbo/go-server/log.yml new file mode 100644 index 0000000000000000000000000000000000000000..59fa4279ad85272c4c49d532beaf23b74d00f58a --- /dev/null +++ b/test/integrate/dubbo/go-server/log.yml @@ -0,0 +1,28 @@ + +level: "debug" +development: true +disableCaller: false +disableStacktrace: false +sampling: +encoding: "console" + +# encoder +encoderConfig: + messageKey: "message" + levelKey: "level" + timeKey: "time" + nameKey: "logger" + callerKey: "caller" + stacktraceKey: "stacktrace" + lineEnding: "" + levelEncoder: "capitalColor" + timeEncoder: "iso8601" + durationEncoder: "seconds" + callerEncoder: "short" + nameEncoder: "" + +outputPaths: + - "stderr" +errorOutputPaths: + - "stderr" +initialFields: diff --git a/test/integrate/dubbo/go-server/server.go b/test/integrate/dubbo/go-server/server.go new file mode 100644 index 0000000000000000000000000000000000000000..4cc6c490835d7ba29d139d71892b5e6e19d628e5 --- /dev/null +++ b/test/integrate/dubbo/go-server/server.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 main + +import ( + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + _ "github.com/apache/dubbo-go/cluster/cluster_impl" + _ "github.com/apache/dubbo-go/cluster/loadbalance" + _ "github.com/apache/dubbo-go/event/proxy/proxy_factory" + "github.com/apache/dubbo-go/config" + _ "github.com/apache/dubbo-go/filter/filter_impl" + _ "github.com/apache/dubbo-go/protocol/dubbo" + _ "github.com/apache/dubbo-go/registry/protocol" + _ "github.com/apache/dubbo-go/registry/zookeeper" +) + +var ( + stopC = make(chan struct{}) +) + +// they are necessary: +// export CONF_PROVIDER_FILE_PATH="xxx" +// export APP_LOG_CONF_FILE="xxx" +func main() { + + hessian.RegisterPOJO(&User{}) + config.Load() + + select { + case <-stopC: + // wait getty send resp to consumer + time.Sleep(3*time.Second) + return + case <-time.After(time.Minute): + panic("provider already running 1 min, but can't be call by consumer") + } +} diff --git a/test/integrate/dubbo/go-server/server.yml b/test/integrate/dubbo/go-server/server.yml new file mode 100644 index 0000000000000000000000000000000000000000..8a17297b10119c217e68b39d58a10493f6dfc7a7 --- /dev/null +++ b/test/integrate/dubbo/go-server/server.yml @@ -0,0 +1,57 @@ +# dubbo server yaml configure file + + +# application config +application: + organization : "ikurento.com" + name : "BDTService" + module : "dubbogo user-info server" + version : "0.0.1" + owner : "ZX" + environment : "dev" + +registries : + "demoZk": + protocol: "zookeeper" + timeout : "3s" + address: "127.0.0.1:2181" + +services: + "UserProvider": + # 鍙互鎸囧畾澶氫釜registry锛屼娇鐢ㄩ€楀彿闅斿紑;涓嶆寚瀹氶粯璁ゅ悜鎵€鏈夋敞鍐屼腑蹇冩敞鍐� + registry: "demoZk" + protocol : "dubbo" + # 鐩稿綋浜巇ubbo.xml涓殑interface + interface : "com.ikurento.user.UserProvider" + loadbalance: "random" + warmup: "100" + cluster: "failover" + methods: + - name: "GetUser" + retries: 1 + loadbalance: "random" + +protocols: + "dubbo": + name: "dubbo" + port: 20000 + + +protocol_conf: + dubbo: + session_number: 700 + session_timeout: "20s" + getty_session_param: + compress_encoding: false + tcp_no_delay: true + tcp_keep_alive: true + keep_alive_period: "120s" + tcp_r_buf_size: 262144 + tcp_w_buf_size: 65536 + pkg_rq_size: 1024 + pkg_wq_size: 512 + tcp_read_timeout: "1s" + tcp_write_timeout: "5s" + wait_timeout: "1s" + max_msg_len: 1024000 + session_name: "server" diff --git a/test/integrate/dubbo/go-server/user.go b/test/integrate/dubbo/go-server/user.go new file mode 100644 index 0000000000000000000000000000000000000000..7bff41566152940f885dec2eb256b261d04ad59a --- /dev/null +++ b/test/integrate/dubbo/go-server/user.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 main + +import ( + "context" + "fmt" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/apache/dubbo-go/config" +) + +func init() { + config.SetProviderService(new(UserProvider)) + // ------for hessian2------ + hessian.RegisterPOJO(&User{}) +} + +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) { + println("req:%#v", req) + rsp := User{"A001", "Alex Stocks", 18, time.Now()} + println("rsp:%#v", rsp) + close(stopC) + return &rsp, nil +} + +func (u *UserProvider) Reference() string { + return "UserProvider" +} + +func (u User) JavaClassName() string { + return "com.ikurento.user.User" +} + +func println(format string, args ...interface{}) { + fmt.Printf("\033[32;40m"+format+"\033[0m\n", args...) +} diff --git a/test/integrate/dubbo/go-server/version.go b/test/integrate/dubbo/go-server/version.go new file mode 100644 index 0000000000000000000000000000000000000000..c6138584f1ddeab3a4927774f44f9e78a8f08da7 --- /dev/null +++ b/test/integrate/dubbo/go-server/version.go @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +var ( + Version = "2.6.0" +)