diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3eb1ec055f18f29a7886e01c24e10c97f88fb1e8..9daa31016dca8890ef8ce64396e0f6ebe9a41462 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,5 @@ -<!-- Thanks for sending a pull request! +<!-- Thanks for sending a pull request! +Read https://github.com/apache/dubbo-go/blob/master/contributing.md before commit pull request. --> **What this PR does**: diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..6ce02861026b4ee7e13f199c29ca74a9f78ffd32 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + target-branch: "develop" + + - package-ecosystem: "github-actions" + # Workflow files stored in the + # default location of `.github/workflows` + directory: "/" + schedule: + interval: "weekly" + target-branch: "develop" \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000000000000000000000000000000000..f2e185cc1eb21e0ac3458f06ef21c407c8e0d06f --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,54 @@ +name: "CodeQL" + +on: + push: + branches: [master, ] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 4 * * 5' + +jobs: + analyse: + name: Analyse + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # 鈩癸笍 Command-line programs to run using the OS shell. + # 馃摎 https://git.io/JvXDl + + # 鉁忥笍 If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000000000000000000000000000000000000..79f6f2066e38f91d862453c39f0c25797546a42a --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: [master, develop] + pull_request: + branches: "*" + +jobs: + + build: + name: ${{ matrix.os }} - Go ${{ matrix.go_version }} + runs-on: ${{ matrix.os }} + strategy: + # If you want to matrix build , you can append the following list. + matrix: + go_version: + - 1.13 + os: + - ubuntu-latest + + env: + DING_TOKEN: ${{ secrets.DING_TOKEN }} + DING_SIGN: ${{ secrets.DING_SIGN }} + + steps: + + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go_version }} + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Cache dependencies + uses: actions/cache@v2 + with: + # Cache + path: ~/go/pkg/mod + # Cache key + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + # An ordered list of keys to use for restoring the cache if no cache hit occurred for key + restore-keys: | + ${{ runner.os }}-go- + + - name: Get dependencies + run: | + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + else + go get -v -t -d ./... + fi + + - name: Verify + run: | + make verify + + - name: Integrate Test + run: | + chmod +x integrate_test.sh && ./integrate_test.sh + + - name: Post Coverage + run: bash <(curl -s https://codecov.io/bash) + + - name: Hello world + run: echo Hello world ${{ secrets.DING_TOKEN }} ${{ secrets.DING_SIGN }} + diff --git a/.gitignore b/.gitignore index fabff68b874df4c2a7de15ce91798e9bb963b358..898962e244d0ba1d030837f76fed47576f38ab5e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,8 +29,12 @@ config_center/zookeeper/zookeeper-4unittest/ registry/zookeeper/zookeeper-4unittest/ metadata/report/zookeeper/zookeeper-4unittest/ registry/consul/agent* +metadata/report/consul/agent* +remoting/consul/agent* config_center/apollo/mockDubbog.properties.json # vim stuff *~ .*.sw? +/license-header-checker-linux/ +/license-header-checker-linux.zip diff --git a/.travis.yml b/.travis.yml index 566c88ece05bd80175eea2d1de8fd061a279e273..4f79ecf3bc1439e00c88c4d0de74cc7a4bf26909 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,16 +15,9 @@ 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 + # license-check + - make verify # integrate-test - chmod +x integrate_test.sh && ./integrate_test.sh diff --git a/CHANGE.md b/CHANGE.md index 90cb5a1443e8062125cbb9f2b3cc0ac1cf759d06..e60e0b05039136305bd789d9347b100a55b90ba9 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -1,5 +1,54 @@ # Release Notes --- +## 1.4.5 + +### Bugfixes +- [Fix too many files open error](https://github.com/apache/dubbo-go/pull/828) [@wenxuwan](https://github.com/wenxuwan) Milestone: [https://github.com/apache/dubbo-go/milestone/6](https://github.com/apache/dubbo-go/milestone/6?closed=1) + +## 1.5.4 + +### Bugfixes +- [Fix etcd cluster reconnect](https://github.com/apache/dubbo-go/pull/828) +- [Fix zookeeper deadlock problem](https://github.com/apache/dubbo-go/pull/826) +- [Fix generic struct2MapAll](https://github.com/apache/dubbo-go/pull/822) +- [Fix Consumer panic when restart provider](https://github.com/apache/dubbo-go/pull/803) +- [Fix etcd can not registry](https://github.com/apache/dubbo-go/pull/819) [@lin-jianjun](https://github.com/lin-jianjun) +- [Fix cannot call go provider service when used by java dubbo 2.7.7 version](https://github.com/apache/dubbo-go/pull/815) [@jack15083](https://github.com/jack15083) +- [Fix go client quit abnormally when it connects java server](https://github.com/apache/dubbo-go/pull/820) [@wenxuwan](https://github.com/wenxuwan) +- [Fix sentinel windows issue](https://github.com/apache/dubbo-go/pull/821) [@louyuting](https://github.com/louyuting) +- [Fix metadata default port](https://github.com/apache/dubbo-go/pull/821) [@sanxun0325](https://github.com/sanxun0325) +- [Fix consul can not destory](https://github.com/apache/dubbo-go/pull/788) [@LaurenceLiZhixin](https://github.com/LaurenceLiZhixin) + +Milestone: [https://github.com/apache/dubbo-go/milestone/6](https://github.com/apache/dubbo-go/milestone/6?closed=1) + +## 1.5.3 + +### New Features +- [Add consul service discovery](https://github.com/apache/dubbo-go/pull/701) [@zhangshen023](https://github.com/zhangshen023) +- [Add File system service discovery](https://github.com/apache/dubbo-go/pull/732) [@DogBaoBao](https://github.com/DogBaoBao) +- [Migrate travis Ci to Github Actions](https://github.com/apache/dubbo-go/pull/752) [@sdttttt](https://github.com/sdttttt) +- [Add sentinel-golang flow control/circuit breaker](https://github.com/apache/dubbo-go/pull/748) [@louyuting](https://github.com/louyuting) +- [Add dubbo-go docs and blog into doc directory](https://github.com/apache/dubbo-go/pull/767) [@oaoit](https://github.com/oaoit) + +### Enhancement +- [Add address notification batch mode](https://github.com/apache/dubbo-go/pull/741) [@beiwei30](https://github.com/beiwei30) +- [Refactor network and codec model](https://github.com/apache/dubbo-go/pull/673) [@fangyincheng](https://github.com/fangyincheng) [@georgehao](https://github.com/georgehao) +- [Remove unnecessary return and judgement](https://github.com/apache/dubbo-go/pull/730) [@YongHaoWu](https://github.com/YongHaoWu) +- [Improve exporter append method](https://github.com/apache/dubbo-go/pull/722) [@gaoxinge](https://github.com/gaoxinge) +- [Refactor for proxyInvoker cannot be extended](https://github.com/apache/dubbo-go/pull/747) [@cvictory](https://github.com/cvictory) +- [Refactor attachment type from map\[string\]stiring to map\[string\]interface{}](https://github.com/apache/dubbo-go/pull/713) [@cvictory](https://github.com/cvictory) +- [Improve map access concurrency](https://github.com/apache/dubbo-go/pull/739) [@skyao](https://github.com/skyao) +- [Improve code quantity](https://github.com/apache/dubbo-go/pull/763) [@gaoxinge](https://github.com/gaoxinge) + +### Bugfixes +- [Fix etcdv3 lease](https://github.com/apache/dubbo-go/pull/738) [@zhangshen023](https://github.com/zhangshen023) +- [Fix rename SethealthChecker to SetHealthChecker](https://github.com/apache/dubbo-go/pull/746) [@watermelo](https://github.com/watermelo) +- [Fix init config problem in HystrixFilter](https://github.com/apache/dubbo-go/pull/731) [@YGrylls](https://github.com/YGrylls) +- [Fix zookeeper listener report error after started](https://github.com/apache/dubbo-go/pull/735) [@wenxuwan](https://github.com/wenxuwan) + +Milestone: [https://github.com/apache/dubbo-go/milestone/4](https://github.com/apache/dubbo-go/milestone/4?closed=1) + +Project: [https://github.com/apache/dubbo-go/projects/10](https://github.com/apache/dubbo-go/projects/10) ## 1.5.1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f6c9bd4e21a56fa4a6060db8795ba6e716160bb --- /dev/null +++ b/Makefile @@ -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. +# + +VERSION ?= latest + +GO = go +GO_PATH = $(shell $(GO) env GOPATH) +GO_OS = $(shell $(GO) env GOOS) +ifeq ($(GO_OS), darwin) + GO_OS = mac +endif +GO_BUILD = $(GO) build +GO_GET = $(GO) get +GO_TEST = $(GO) test +GO_BUILD_FLAGS = -v +GO_BUILD_LDFLAGS = -X main.version=$(VERSION) + +GO_LICENSE_CHECKER_DIR = license-header-checker-$(GO_OS) +GO_LICENSE_CHECKER = $(GO_PATH)/bin/license-header-checker +LICENSE_DIR = /tmp/tools/license + +ARCH = amd64 +# for add zookeeper fatjar +ZK_TEST_LIST=config_center/zookeeper registry/zookeeper cluster/router/chain cluster/router/condition cluster/router/tag metadata/report/zookeeper +ZK_JAR_NAME=zookeeper-3.4.9-fatjar.jar +ZK_FATJAR_BASE=/zookeeper-4unittest/contrib/fatjar +ZK_JAR_PATH=remoting/zookeeper$(ZK_FATJAR_BASE) +ZK_JAR=$(ZK_JAR_PATH)/$(ZK_JAR_NAME) + +SHELL = /bin/bash + +prepareLic: + $(GO_LICENSE_CHECKER) -version || (wget https://github.com/lsm-dev/license-header-checker/releases/download/v1.2.0/$(GO_LICENSE_CHECKER_DIR).zip -O $(GO_LICENSE_CHECKER_DIR).zip && unzip -o $(GO_LICENSE_CHECKER_DIR).zip && mkdir -p $(GO_PATH)/bin/ && cp $(GO_LICENSE_CHECKER_DIR)/64bit/license-header-checker $(GO_PATH)/bin/) + ls /tmp/tools/license/license.txt || wget -P $(LICENSE_DIR) https://github.com/dubbogo/resources/raw/master/tools/license/license.txt + +prepareZk: + ls $(ZK_JAR) || (mkdir -p $(ZK_JAR_PATH)&& wget -P $(ZK_JAR_PATH) https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/${ZK_JAR_NAME}) + @for i in $(ZK_TEST_LIST); do \ + mkdir -p $$i$(ZK_FATJAR_BASE);\ + cp ${ZK_JAR} $$i$(ZK_FATJAR_BASE);\ + done + +prepare: prepareZk prepareLic + +.PHONE: test +test: clean prepareZk + $(GO_TEST) ./... -coverprofile=coverage.txt -covermode=atomic + +deps: prepare + $(GO_GET) -v -t -d ./... + +.PHONY: license +license: clean prepareLic + $(GO_LICENSE_CHECKER) -v -a -r -i vendor $(LICENSE_DIR)/license.txt . go && [[ -z `git status -s` ]] + +.PHONY: verify +verify: clean license test + +.PHONY: clean +clean: prepare + rm -rf coverage.txt + rm -rf license-header-checker* diff --git a/README.md b/README.md index 9e1edd3af1cd8957b1daa9b9fe2cadf121bc2d6d..f950a4b3e9c4e115eda2f0e99379286c4c66d24f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,14 @@ Apache License, Version 2.0 ## Release note ## +[v1.4.5 - Nov 18, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.4.5) + +[v1.5.4 - Nov 1, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.5.4) + +[v1.5.3 - Sep 23, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.5.3) + +[v1.5.2 - discard]() + [v1.5.1 - Aug 23, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.5.1) [v1.5.0 - July 24, 2020](https://github.com/apache/dubbo-go/releases/tag/v1.5.0) @@ -60,7 +68,7 @@ Finished List: * Jsonrpc2.0 * [gRPC](https://github.com/apache/dubbo-go/pull/311) * [RESTful](https://github.com/apache/dubbo-go/pull/352) - + - Router * [Condition router](https://github.com/apache/dubbo-go/pull/294) * [Health check router](https://github.com/apache/dubbo-go/pull/389) @@ -105,7 +113,7 @@ Finished List: - Invoke * [generic invoke](https://github.com/apache/dubbo-go/pull/122) - + - Monitor * Opentracing API * [Prometheus](https://github.com/apache/dubbo-go/pull/342) @@ -144,28 +152,26 @@ https://dubbogo.github.io/dubbo-go-website (**Improving**) ## Quick Start -[dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) shows how to use dubbo-go. Please read the [dubbo-samples/golang/README.md](https://github.com/dubbogo/dubbo-samples/blob/master/golang/README.md) carefully to learn how to dispose the configuration and compile the program. +[dubbo-go-samples](https://github.com/apache/dubbo-go-samples) shows how to use dubbo-go. Please read the [dubbo-samples/golang/README.md](https://github.com/apache/dubbo-go-samples/blob/master/README.md) carefully to learn how to dispose the configuration and compile the program. ## Running unit tests -### Prepare +### Run -Mac/Linux ```bash -sh ./before_ut.sh +make verify ``` -Windows +### Verify license + ```bash -before_ut.bat +make license ``` -### Run -```bash -go test ./... +### Run unit test -# coverage -go test ./... -coverprofile=coverage.txt -covermode=atomic +```bash +make test ``` ## Build @@ -212,8 +218,49 @@ If you are using [apache/dubbo-go](github.com/apache/dubbo-go) and think that it <img width="222px" src="https://raw.githubusercontent.com/mosn/community/master/icons/png/mosn-labeled-horizontal.png"> </a> </td> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://festatic.estudy.cn/assets/xhx-web/layout/logo.png"> + </a> + </td> + </tr> + <tr></tr> + <tr> + <td align="center" valign="middle"> + <a href="http://www.j.cn" target="_blank"> + <img width="222px" src="http://image.guang.j.cn/bbs/imgs/home/pc/icon_8500.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://www.genshuixue.com/" target="_blank"> + <img width="222px" src="https://i.gsxcdn.com/0cms/d/file/content/2020/02/5e572137d7d94.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="http://www.51h5.com" target="_blank"> + <img width="222px" src="https://fs-ews.51h5.com/common/hw_220_black.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://www.zto.com" target="_blank"> + <img width="222px" src="https://fscdn.zto.com/fs8/M02/B2/E4/wKhBD1-8o52Ae3GnAAASU3r62ME040.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://www.icsoc.net/" target="_blank"> + <img width="222px" src="https://oss.icsoc.net/icsoc-ekt-test-files/icsoc.png"> + </a> + </td> </tr> <tr></tr> + <tr> + <td align="center" valign="middle"> + <a href="http://www.mgtv.com" target="_blank"> + <img width="222px" src="https://ugc.hitv.com/platform_oss/F6077F1AA82542CDBDD88FD518E6E727.png"> + </a> + </td> + </tr> + <tr></tr> </tbody> </table> </div> diff --git a/README_CN.md b/README_CN.md index b76d8983deae427f9317c4f930f0e06da479f484..ff72428d977f8b24c7c60b228108987c823c9efa 100644 --- a/README_CN.md +++ b/README_CN.md @@ -15,6 +15,16 @@ Apache License, Version 2.0 ## 鍙戝竷鏃ュ織 ## +[v1.4.5 - 2020骞�11鏈�18鏃(https://github.com/apache/dubbo-go/releases/tag/v1.4.5) + +[v1.5.4 - 2020骞�11鏈�1鏃(https://github.com/apache/dubbo-go/releases/tag/v1.5.4) + +[v1.5.3 - 2020骞�9鏈�23鏃(https://github.com/apache/dubbo-go/releases/tag/v1.5.3) + +[v1.5.2 - 鑸嶅純]() + +[v1.5.1 - 2020骞�8鏈�23鏃(https://github.com/apache/dubbo-go/releases/tag/v1.5.1) + [v1.5.0 - 2020骞�7鏈�24鏃(https://github.com/apache/dubbo-go/releases/tag/v1.5.0) [v1.4.0 - 2020骞�3鏈�17鏃(https://github.com/apache/dubbo-go/releases/tag/v1.4.0) @@ -95,6 +105,7 @@ 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) + * [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) @@ -144,29 +155,27 @@ https://dubbogo.github.io/dubbo-go-website (**瀹屽杽涓�**) ## 杩愯鍗曟祴 -### 鍑嗗 +### 鎵ц鍏ㄩ儴鏍¢獙 -Mac/Linux ```bash -sh ./before_ut.sh +make verify ``` -Windows +### 鏍¢獙璁稿彲璇� + ```bash -before_ut.bat +make license ``` -### 鎵ц -```bash -go test ./... +### 鎵ц鍗曞厓娴嬭瘯 -# coverage -go test ./... -coverprofile=coverage.txt -covermode=atomic +```bash +make test ``` ## 缂栬瘧 -璇风Щ姝� [dubbo-samples/golang](https://github.com/dubbogo/dubbo-samples) +璇风Щ姝� [dubbo-go-samples](https://github.com/apache/dubbo-go-samples) ## 濡備綍璐$尞 @@ -207,8 +216,49 @@ go test ./... -coverprofile=coverage.txt -covermode=atomic <img width="222px" src="https://raw.githubusercontent.com/mosn/community/master/icons/png/mosn-labeled-horizontal.png"> </a> </td> + <td align="center" valign="middle"> + <a href="" target="_blank"> + <img width="222px" src="https://festatic.estudy.cn/assets/xhx-web/layout/logo.png"> + </a> + </td> </tr> <tr></tr> + <tr> + <td align="center" valign="middle"> + <a href="http://www.j.cn" target="_blank"> + <img width="222px" src="http://image.guang.j.cn/bbs/imgs/home/pc/icon_8500.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://www.genshuixue.com/" target="_blank"> + <img width="222px" src="https://i.gsxcdn.com/0cms/d/file/content/2020/02/5e572137d7d94.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="http://www.51h5.com" target="_blank"> + <img width="222px" src="https://fs-ews.51h5.com/common/hw_220_black.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://www.zto.com" target="_blank"> + <img width="222px" src="https://fscdn.zto.com/fs8/M02/B2/E4/wKhBD1-8o52Ae3GnAAASU3r62ME040.png"> + </a> + </td> + <td align="center" valign="middle"> + <a href="https://www.icsoc.net/" target="_blank"> + <img width="222px" src="https://oss.icsoc.net/icsoc-ekt-test-files/icsoc.png"> + </a> + </td> + </tr> + <tr></tr> + <tr> + <td align="center" valign="middle"> + <a href="http://www.mgtv.com" target="_blank"> + <img width="222px" src="https://ugc.hitv.com/platform_oss/F6077F1AA82542CDBDD88FD518E6E727.png"> + </a> + </td> + </tr> + <tr></tr> </tbody> </table> -</div> +</div> \ No newline at end of file diff --git a/before_ut.bat b/before_ut.bat deleted file mode 100644 index 7f5cf50e900955f784552221569d9caf414274d4..0000000000000000000000000000000000000000 --- a/before_ut.bat +++ /dev/null @@ -1,43 +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. - -set zkJarName=zookeeper-3.4.9-fatjar.jar -set remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/%zkJarName%" -set zkJarPath=remoting/zookeeper/zookeeper-4unittest/contrib/fatjar -set zkJar=%zkJarPath%/%zkJarName% - -if not exist "%zkJar%" ( - md "%zkJarPath%" - curl -L %remoteJarUrl% -o %zkJar% -) - -md config_center\zookeeper\zookeeper-4unittest\contrib\fatjar -xcopy /f "%zkJar%" "config_center/zookeeper/zookeeper-4unittest/contrib/fatjar/" - -md registry\zookeeper\zookeeper-4unittest\contrib\fatjar -xcopy /f "%zkJar%" "registry/zookeeper/zookeeper-4unittest/contrib/fatjar/" - -md cluster\router\chain\zookeeper-4unittest\contrib\fatjar -xcopy /f "%zkJar%" "cluster/router/chain/zookeeper-4unittest/contrib/fatjar/" - -md cluster\router\condition\zookeeper-4unittest\contrib\fatjar -xcopy /f "%zkJar%" "cluster/router/condition/zookeeper-4unittest/contrib/fatjar/" - -mkdir -p cluster/router/tag/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} cluster/router/tag/zookeeper-4unittest/contrib/fatjar - -md metadata\report\zookeeper\zookeeper-4unittest\contrib\fatjar -xcopy /f "%zkJar%" "metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar/" \ No newline at end of file diff --git a/before_ut.sh b/before_ut.sh deleted file mode 100755 index b55e424ef72b33181b2ea40fdb37ac319110aec0..0000000000000000000000000000000000000000 --- a/before_ut.sh +++ /dev/null @@ -1,43 +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. - -zkJarName="zookeeper-3.4.9-fatjar.jar" -remoteJarUrl="https://github.com/dubbogo/resources/raw/master/zookeeper-4unitest/contrib/fatjar/${zkJarName}" -zkJarPath="remoting/zookeeper/zookeeper-4unittest/contrib/fatjar" -zkJar="${zkJarPath}/${zkJarName}" - -if [ ! -f "${zkJar}" ]; then - mkdir -p ${zkJarPath} - wget -P "${zkJarPath}" ${remoteJarUrl} -fi - -mkdir -p config_center/zookeeper/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} config_center/zookeeper/zookeeper-4unittest/contrib/fatjar - -mkdir -p registry/zookeeper/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} registry/zookeeper/zookeeper-4unittest/contrib/fatjar - -mkdir -p cluster/router/chain/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} cluster/router/chain/zookeeper-4unittest/contrib/fatjar - -mkdir -p cluster/router/condition/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} cluster/router/condition/zookeeper-4unittest/contrib/fatjar - -mkdir -p cluster/router/tag/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} cluster/router/tag/zookeeper-4unittest/contrib/fatjar - -mkdir -p metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar -cp ${zkJar} metadata/report/zookeeper/zookeeper-4unittest/contrib/fatjar \ No newline at end of file diff --git a/before_validate_license.sh b/before_validate_license.sh deleted file mode 100644 index 8fa6e381c7a4cd44835d107ba9213f685f899a10..0000000000000000000000000000000000000000 --- a/before_validate_license.sh +++ /dev/null @@ -1,26 +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. - -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_impl/base_cluster_invoker.go b/cluster/cluster_impl/base_cluster_invoker.go index ced5b15cb9a1c2292ca866f6f7478ce2b23a30b9..ed30559ed3c4b3930e65d6e66d5e91d8619516a1 100644 --- a/cluster/cluster_impl/base_cluster_invoker.go +++ b/cluster/cluster_impl/base_cluster_invoker.go @@ -22,7 +22,6 @@ import ( ) import ( - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "go.uber.org/atomic" ) @@ -32,6 +31,7 @@ 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/protocol" ) @@ -51,7 +51,7 @@ func newBaseClusterInvoker(directory cluster.Directory) baseClusterInvoker { } } -func (invoker *baseClusterInvoker) GetUrl() common.URL { +func (invoker *baseClusterInvoker) GetUrl() *common.URL { return invoker.directory.GetUrl() } @@ -72,7 +72,7 @@ func (invoker *baseClusterInvoker) IsAvailable() bool { //check invokers availables func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, invocation protocol.Invocation) error { if len(invokers) == 0 { - ip, _ := gxnet.GetLocalIP() + ip := common.GetLocalIp() return perrors.Errorf("Failed to invoke the method %v. No provider available for the service %v from "+ "registry %v on the consumer %v using the dubbo version %v .Please check if the providers have been started and registered.", invocation.MethodName(), invoker.directory.GetUrl().SubURL.Key(), invoker.directory.GetUrl().String(), ip, constant.Version) @@ -84,7 +84,7 @@ func (invoker *baseClusterInvoker) checkInvokers(invokers []protocol.Invoker, in //check cluster invoker is destroyed or not func (invoker *baseClusterInvoker) checkWhetherDestroyed() error { if invoker.destroyed.Load() { - ip, _ := gxnet.GetLocalIP() + ip := common.GetLocalIp() return perrors.Errorf("Rpc cluster invoker for %v on consumer %v use dubbo version %v is now destroyed! can not invoke any more. ", invoker.directory.GetUrl().Service(), ip, constant.Version) } @@ -120,6 +120,10 @@ func (invoker *baseClusterInvoker) doSelect(lb cluster.LoadBalance, invocation p } func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invocation protocol.Invocation, invokers []protocol.Invoker, invoked []protocol.Invoker) protocol.Invoker { + if len(invokers) == 0 { + logger.Errorf("the invokers of %s is nil. ", invocation.Invoker().GetUrl().ServiceKey()) + return nil + } if len(invokers) == 1 { return invokers[0] } @@ -134,6 +138,8 @@ func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invoc for _, invoker := range invokers { if !invoker.IsAvailable() { + logger.Infof("the invoker of %s is not available, maybe some network error happened or the server is shutdown.", + invoker.GetUrl().Ip) continue } @@ -145,6 +151,7 @@ func (invoker *baseClusterInvoker) doSelectInvoker(lb cluster.LoadBalance, invoc if len(reslectInvokers) > 0 { selectedInvoker = lb.Select(reslectInvokers, invocation) } else { + logger.Errorf("all %d invokers is unavailable for %s.", len(invokers), selectedInvoker.GetUrl().String()) return nil } } diff --git a/cluster/cluster_impl/failover_cluster_invoker.go b/cluster/cluster_impl/failover_cluster_invoker.go index 4260a9324dd360ac24f80e425d8542a423c8815e..ca490e7f8e2ff44303abe7d695313153b7466a00 100644 --- a/cluster/cluster_impl/failover_cluster_invoker.go +++ b/cluster/cluster_impl/failover_cluster_invoker.go @@ -24,12 +24,12 @@ import ( ) import ( - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) import ( "github.com/apache/dubbo-go/cluster" + "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" @@ -51,6 +51,7 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr result protocol.Result invoked []protocol.Invoker providers []string + ivk protocol.Invoker ) invokers := invoker.directory.List(invocation) @@ -75,7 +76,7 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr return &protocol.RPCResult{Err: err} } } - ivk := invoker.doSelect(loadBalance, invocation, invokers, invoked) + ivk = invoker.doSelect(loadBalance, invocation, invokers, invoked) if ivk == nil { continue } @@ -88,10 +89,17 @@ func (invoker *failoverClusterInvoker) Invoke(ctx context.Context, invocation pr } return result } - - ip, _ := gxnet.GetLocalIP() + ip := common.GetLocalIp() invokerSvc := invoker.GetUrl().Service() invokerUrl := invoker.directory.GetUrl() + if ivk == nil { + logger.Errorf("Failed to invoke the method %s of the service %s .No provider is available.", methodName, invokerSvc) + return &protocol.RPCResult{ + Err: perrors.Errorf("Failed to invoke the method %s of the service %s .No provider is available because can't connect server.", + methodName, invokerSvc), + } + } + return &protocol.RPCResult{ Err: perrors.Wrap(result.Error(), fmt.Sprintf("Failed to invoke the method %v in the service %v. "+ "Tried %v times of the providers %v (%v/%v)from the registry %v on the consumer %v using the dubbo version %v. "+ diff --git a/cluster/cluster_impl/failover_cluster_test.go b/cluster/cluster_impl/failover_cluster_test.go index d3ac2c8a5ffd7fce647649c53fe343ba93999636..3ea6232d4747f722c5a933cc056ff06304913965 100644 --- a/cluster/cluster_impl/failover_cluster_test.go +++ b/cluster/cluster_impl/failover_cluster_test.go @@ -45,7 +45,7 @@ import ( // nolint type MockInvoker struct { - url common.URL + url *common.URL available bool destroyed bool @@ -53,7 +53,7 @@ type MockInvoker struct { } // nolint -func NewMockInvoker(url common.URL, successCount int) *MockInvoker { +func NewMockInvoker(url *common.URL, successCount int) *MockInvoker { return &MockInvoker{ url: url, available: true, @@ -63,7 +63,7 @@ func NewMockInvoker(url common.URL, successCount int) *MockInvoker { } // nolint -func (bi *MockInvoker) GetUrl() common.URL { +func (bi *MockInvoker) GetUrl() *common.URL { return bi.url } diff --git a/cluster/cluster_impl/forking_cluster_invoker.go b/cluster/cluster_impl/forking_cluster_invoker.go index 168444881653ca38ef61a9bc8e50f2d4bc3e624c..3ffda580ce9847bb60a0a837c5fac6b420450141 100644 --- a/cluster/cluster_impl/forking_cluster_invoker.go +++ b/cluster/cluster_impl/forking_cluster_invoker.go @@ -56,7 +56,7 @@ func (invoker *forkingClusterInvoker) Invoke(ctx context.Context, invocation pro } var selected []protocol.Invoker - forks := int(invoker.GetUrl().GetParamInt(constant.FORKS_KEY, constant.DEFAULT_FORKS)) + forks := invoker.GetUrl().GetParamByIntValue(constant.FORKS_KEY, constant.DEFAULT_FORKS) timeouts := invoker.GetUrl().GetParamInt(constant.TIMEOUT_KEY, constant.DEFAULT_TIMEOUT) if forks < 0 || forks > len(invokers) { selected = invokers diff --git a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go index cd201a42c759354ca536ea3e9e77116d89ea8b4b..7f77f33166de293836c15391f5eedd5a18084dbe 100644 --- a/cluster/cluster_impl/zone_aware_cluster_invoker_test.go +++ b/cluster/cluster_impl/zone_aware_cluster_invoker_test.go @@ -44,7 +44,7 @@ func TestZoneWareInvokerWithPreferredSuccess(t *testing.T) { //defer ctrl.Finish() mockResult := &protocol.RPCResult{ - Attrs: map[string]string{constant.PREFERRED_KEY: "true"}, + Attrs: map[string]interface{}{constant.PREFERRED_KEY: "true"}, Rest: rest{tried: 0, success: true}} var invokers []protocol.Invoker @@ -99,7 +99,7 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { return &protocol.RPCResult{ - Attrs: map[string]string{constant.WEIGHT_KEY: w1}, + Attrs: map[string]interface{}{constant.WEIGHT_KEY: w1}, Rest: rest{tried: 0, success: true}} }).MaxTimes(100) } else { @@ -107,7 +107,7 @@ func TestZoneWareInvokerWithWeightSuccess(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { return &protocol.RPCResult{ - Attrs: map[string]string{constant.WEIGHT_KEY: w2}, + Attrs: map[string]interface{}{constant.WEIGHT_KEY: w2}, Rest: rest{tried: 0, success: true}} }).MaxTimes(100) } @@ -154,7 +154,7 @@ func TestZoneWareInvokerWithZoneSuccess(t *testing.T) { invoker.EXPECT().Invoke(gomock.Any()).DoAndReturn( func(invocation protocol.Invocation) protocol.Result { return &protocol.RPCResult{ - Attrs: map[string]string{constant.ZONE_KEY: zoneValue}, + Attrs: map[string]interface{}{constant.ZONE_KEY: zoneValue}, Rest: rest{tried: 0, success: true}} }) invokers = append(invokers, invoker) diff --git a/cluster/directory/base_directory.go b/cluster/directory/base_directory.go index 20db1f2b7de71c843caf7c7abda39e40c68e4ecd..d1025a152b599d70c40bba5bc16009d8d5adee37 100644 --- a/cluster/directory/base_directory.go +++ b/cluster/directory/base_directory.go @@ -65,8 +65,8 @@ func (dir *BaseDirectory) SetRouterChain(routerChain router.Chain) { } // GetUrl Get URL -func (dir *BaseDirectory) GetUrl() common.URL { - return *dir.url +func (dir *BaseDirectory) GetUrl() *common.URL { + return dir.url } // GetDirectoryUrl Get URL instance diff --git a/cluster/directory/base_directory_test.go b/cluster/directory/base_directory_test.go index a2b62dfa008e6cd17b1200d93cd235da17d03905..16e3c5a960912af66739bcc1c5736d44d437503e 100644 --- a/cluster/directory/base_directory_test.go +++ b/cluster/directory/base_directory_test.go @@ -24,7 +24,6 @@ import ( ) import ( - gxnet "github.com/dubbogo/gost/net" "github.com/stretchr/testify/assert" ) @@ -41,21 +40,18 @@ var ( ) func TestNewBaseDirectory(t *testing.T) { - directory := NewBaseDirectory(&url) - assert.NotNil(t, directory) - assert.Equal(t, url, directory.GetUrl()) - assert.Equal(t, &url, directory.GetDirectoryUrl()) + dir := NewBaseDirectory(url) + assert.Equal(t, url, dir.GetUrl()) + assert.Equal(t, url, dir.GetDirectoryUrl()) } func TestBuildRouterChain(t *testing.T) { regURL := url regURL.AddParam(constant.INTERFACE_KEY, "mock-app") - directory := NewBaseDirectory(®URL) + directory := NewBaseDirectory(regURL) - assert.NotNil(t, directory) - - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) routeURL := getRouteURL(rule, anyURL) routeURL.AddParam(constant.INTERFACE_KEY, "mock-app") @@ -67,19 +63,19 @@ func TestBuildRouterChain(t *testing.T) { assert.NotNil(t, chain) } -func getRouteURL(rule string, u common.URL) *common.URL { +func getRouteURL(rule string, u *common.URL) *common.URL { ru := u ru.AddParam("rule", rule) ru.AddParam("force", "true") ru.AddParam(constant.ROUTER_KEY, "router") - return &ru + return ru } func TestIsProperRouter(t *testing.T) { regURL := url regURL.AddParam(constant.APPLICATION_KEY, "mock-app") - d := NewBaseDirectory(®URL) - localIP, _ := gxnet.GetLocalIP() + d := NewBaseDirectory(regURL) + localIP := common.GetLocalIp() rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) routeURL := getRouteURL(rule, anyURL) routeURL.AddParam(constant.APPLICATION_KEY, "mock-app") @@ -88,7 +84,7 @@ func TestIsProperRouter(t *testing.T) { regURL.AddParam(constant.APPLICATION_KEY, "") regURL.AddParam(constant.INTERFACE_KEY, "com.foo.BarService") - d = NewBaseDirectory(®URL) + d = NewBaseDirectory(regURL) routeURL = getRouteURL(rule, anyURL) routeURL.AddParam(constant.INTERFACE_KEY, "com.foo.BarService") rst = d.isProperRouter(routeURL) @@ -96,14 +92,14 @@ func TestIsProperRouter(t *testing.T) { regURL.AddParam(constant.APPLICATION_KEY, "") regURL.AddParam(constant.INTERFACE_KEY, "") - d = NewBaseDirectory(®URL) + d = NewBaseDirectory(regURL) routeURL = getRouteURL(rule, anyURL) rst = d.isProperRouter(routeURL) assert.True(t, rst) regURL.SetParam(constant.APPLICATION_KEY, "") regURL.SetParam(constant.INTERFACE_KEY, "") - d = NewBaseDirectory(®URL) + d = NewBaseDirectory(regURL) routeURL = getRouteURL(rule, anyURL) routeURL.AddParam(constant.APPLICATION_KEY, "mock-service") rst = d.isProperRouter(routeURL) @@ -111,7 +107,7 @@ func TestIsProperRouter(t *testing.T) { regURL.SetParam(constant.APPLICATION_KEY, "") regURL.SetParam(constant.INTERFACE_KEY, "") - d = NewBaseDirectory(®URL) + d = NewBaseDirectory(regURL) routeURL = getRouteURL(rule, anyURL) routeURL.AddParam(constant.INTERFACE_KEY, "mock-service") rst = d.isProperRouter(routeURL) diff --git a/cluster/directory/static_directory.go b/cluster/directory/static_directory.go index 87f51356495dbd0a956c42bf4f34022b4d21ad4d..d9695d46b3d25dd89a80e10c2396e650492840ad 100644 --- a/cluster/directory/static_directory.go +++ b/cluster/directory/static_directory.go @@ -34,15 +34,18 @@ type staticDirectory struct { // NewStaticDirectory Create a new staticDirectory with invokers func NewStaticDirectory(invokers []protocol.Invoker) *staticDirectory { - var url common.URL + var url *common.URL if len(invokers) > 0 { url = invokers[0].GetUrl() } - return &staticDirectory{ - BaseDirectory: NewBaseDirectory(&url), + dir := &staticDirectory{ + BaseDirectory: NewBaseDirectory(url), invokers: invokers, } + + dir.routerChain.SetInvokers(invokers) + return dir } //for-loop invokers ,if all invokers is available ,then it means directory is available @@ -69,7 +72,7 @@ func (dir *staticDirectory) List(invocation protocol.Invocation) []protocol.Invo return invokers } dirUrl := dir.GetUrl() - return routerChain.Route(invokers, &dirUrl, invocation) + return routerChain.Route(dirUrl, invocation) } // Destroy Destroy @@ -88,10 +91,11 @@ func (dir *staticDirectory) BuildRouterChain(invokers []protocol.Invoker) error return perrors.Errorf("invokers == null") } url := invokers[0].GetUrl() - routerChain, e := chain.NewRouterChain(&url) + routerChain, e := chain.NewRouterChain(url) if e != nil { return e } + routerChain.SetInvokers(dir.invokers) dir.SetRouterChain(routerChain) return nil } diff --git a/cluster/loadbalance/consistent_hash.go b/cluster/loadbalance/consistent_hash.go index 27ce2369f9de6dfe76bd35581ea26f0e0c24e480..3d036b4f3c5c50874e09efb8fd1ef34e969585d8 100644 --- a/cluster/loadbalance/consistent_hash.go +++ b/cluster/loadbalance/consistent_hash.go @@ -105,7 +105,7 @@ func newConsistentHashSelector(invokers []protocol.Invoker, methodName string, selector.virtualInvokers = make(map[uint32]protocol.Invoker) selector.hashCode = hashCode url := invokers[0].GetUrl() - selector.replicaNum = int(url.GetMethodParamInt(methodName, HashNodes, 160)) + selector.replicaNum = url.GetMethodParamIntValue(methodName, HashNodes, 160) indices := re.Split(url.GetMethodParam(methodName, HashArguments, "0"), -1) for _, index := range indices { i, err := strconv.Atoi(index) diff --git a/cluster/loadbalance/consistent_hash_test.go b/cluster/loadbalance/consistent_hash_test.go index 9f22d39dc46243dddda89151e07dbea39ab933fb..0fbb74059cf0af6cf7e75aab646d7c5c146bcc28 100644 --- a/cluster/loadbalance/consistent_hash_test.go +++ b/cluster/loadbalance/consistent_hash_test.go @@ -84,9 +84,9 @@ func TestConsistentHashLoadBalanceSuite(t *testing.T) { type consistentHashLoadBalanceSuite struct { suite.Suite - url1 common.URL - url2 common.URL - url3 common.URL + url1 *common.URL + url2 *common.URL + url3 *common.URL invokers []protocol.Invoker invoker1 protocol.Invoker invoker2 protocol.Invoker diff --git a/cluster/router/chain.go b/cluster/router/chain.go new file mode 100644 index 0000000000000000000000000000000000000000..3614d0a5a3d6cfb462ef63149ae99da2c4541b8d --- /dev/null +++ b/cluster/router/chain.go @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package router + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/protocol" +) + +// Chain +type Chain interface { + Route(*common.URL, protocol.Invocation) []protocol.Invoker + // Refresh invokers + SetInvokers([]protocol.Invoker) + // AddRouters Add routers + AddRouters([]PriorityRouter) +} diff --git a/cluster/router/chain/chain.go b/cluster/router/chain/chain.go index 8746c1daf7f878a066ea005f910520e07c28318c..8c4ffe01bd9fe251fab60743ad5fe2aa83e20653 100644 --- a/cluster/router/chain/chain.go +++ b/cluster/router/chain/chain.go @@ -18,9 +18,10 @@ package chain import ( - "math" "sort" "sync" + "sync/atomic" + "time" ) import ( @@ -30,11 +31,18 @@ import ( import ( "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" ) +const ( + timeInterval = 5 * time.Second + timeThreshold = 2 * time.Second + countThreshold = 5 +) + // RouterChain Router chain type RouterChain struct { // Full list of addresses from registry, classified by method name. @@ -47,31 +55,41 @@ type RouterChain struct { mutex sync.RWMutex - url common.URL + url *common.URL + + // The times of address notification since last update for address cache + count int64 + // The timestamp of last update for address cache + last time.Time + // Channel for notify to update the address cache + notify chan struct{} + // Address cache + cache atomic.Value + // init + init sync.Once } // Route Loop routers in RouterChain and call Route method to determine the target invokers list. -func (c *RouterChain) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - finalInvokers := invokers - l := len(c.routers) - rs := make([]router.PriorityRouter, l, int(math.Ceil(float64(l)*1.2))) - c.mutex.RLock() - copy(rs, c.routers) - c.mutex.RUnlock() +func (c *RouterChain) Route(url *common.URL, invocation protocol.Invocation) []protocol.Invoker { + cache := c.loadCache() + if cache == nil { + c.mutex.RLock() + defer c.mutex.RUnlock() + return c.invokers + } - for _, r := range rs { - finalInvokers = r.Route(finalInvokers, url, invocation) + bitmap := cache.bitmap + for _, r := range c.copyRouters() { + bitmap = r.Route(bitmap, cache, url, invocation) } - return finalInvokers -} -// SetInvokers notify router chain of the initial addresses from registry at the first time. Notify whenever addresses in registry change. -func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { - for _, r := range c.routers { - if notifyRouter, ok := r.(router.NotifyRouter); ok { - notifyRouter.Notify(invokers) - } + indexes := bitmap.ToArray() + finalInvokers := make([]protocol.Invoker, len(indexes)) + for i, index := range indexes { + finalInvokers[i] = cache.invokers[index] } + + return finalInvokers } // AddRouters Add routers to router chain @@ -88,8 +106,122 @@ func (c *RouterChain) AddRouters(routers []router.PriorityRouter) { c.routers = newRouters } +// SetInvokers receives updated invokers from registry center. If the times of notification exceeds countThreshold and +// time interval exceeds timeThreshold since last cache update, then notify to update the cache. +func (c *RouterChain) SetInvokers(invokers []protocol.Invoker) { + c.mutex.Lock() + c.invokers = invokers + c.mutex.Unlock() + + // it should trigger init router for first call + c.init.Do(func() { + go func() { + c.notify <- struct{}{} + }() + }) + + c.count++ + now := time.Now() + if c.count >= countThreshold && now.Sub(c.last) >= timeThreshold { + c.last = now + c.count = 0 + go func() { + c.notify <- struct{}{} + }() + } +} + +// loop listens on events to update the address cache when it's necessary, either when it receives notification +// from address update, or when timeInterval exceeds. +func (c *RouterChain) loop() { + ticker := time.NewTicker(timeInterval) + for { + select { + case <-ticker.C: + c.buildCache() + case <-c.notify: + c.buildCache() + } + } +} + +// copyRouters make a snapshot copy from RouterChain's router list. +func (c *RouterChain) copyRouters() []router.PriorityRouter { + c.mutex.RLock() + defer c.mutex.RUnlock() + ret := make([]router.PriorityRouter, 0, len(c.routers)) + ret = append(ret, c.routers...) + return ret +} + +// copyInvokers copies a snapshot of the received invokers. +func (c *RouterChain) copyInvokers() []protocol.Invoker { + c.mutex.RLock() + defer c.mutex.RUnlock() + if c.invokers == nil || len(c.invokers) == 0 { + return nil + } + ret := make([]protocol.Invoker, 0, len(c.invokers)) + ret = append(ret, c.invokers...) + return ret +} + +// loadCache loads cache from sync.Value to guarantee the visibility +func (c *RouterChain) loadCache() *InvokerCache { + v := c.cache.Load() + if v == nil { + return nil + } + + return v.(*InvokerCache) +} + +// copyInvokerIfNecessary compares chain's invokers copy and cache's invokers copy, to avoid copy as much as possible +func (c *RouterChain) copyInvokerIfNecessary(cache *InvokerCache) []protocol.Invoker { + var invokers []protocol.Invoker + if cache != nil { + invokers = cache.invokers + } + + c.mutex.RLock() + defer c.mutex.RUnlock() + if isInvokersChanged(invokers, c.invokers) { + invokers = c.copyInvokers() + } + return invokers +} + +// buildCache builds address cache with the new invokers for all poolable routers. +func (c *RouterChain) buildCache() { + origin := c.loadCache() + invokers := c.copyInvokerIfNecessary(origin) + + var ( + mutex sync.Mutex + wg sync.WaitGroup + ) + + cache := BuildCache(invokers) + for _, r := range c.copyRouters() { + if p, ok := r.(router.Poolable); ok { + wg.Add(1) + go func(p router.Poolable) { + defer wg.Done() + pool, info := poolRouter(p, origin, invokers) + mutex.Lock() + defer mutex.Unlock() + cache.pools[p.Name()] = pool + cache.metadatas[p.Name()] = info + }(p) + } + } + wg.Wait() + + c.cache.Store(cache) +} + // URL Return URL in RouterChain -func (c *RouterChain) URL() common.URL { +func (c *RouterChain) URL() *common.URL { return c.url } @@ -118,14 +250,64 @@ func NewRouterChain(url *common.URL) (*RouterChain, error) { chain := &RouterChain{ builtinRouters: routers, routers: newRouters, + last: time.Now(), + notify: make(chan struct{}), } if url != nil { - chain.url = *url + chain.url = url } + go chain.loop() return chain, nil } +// poolRouter calls poolable router's Pool() to create new address pool and address metadata if necessary. +// If the corresponding cache entry exists, and the poolable router answers no need to re-pool (possibly because its +// rule doesn't change), and the address list doesn't change, then the existing data will be re-used. +func poolRouter(p router.Poolable, origin *InvokerCache, invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { + name := p.Name() + if isCacheMiss(origin, name) || p.ShouldPool() || &(origin.invokers) != &invokers { + logger.Debugf("build address cache for router %q", name) + return p.Pool(invokers) + } + + logger.Debugf("reuse existing address cache for router %q", name) + return origin.pools[name], origin.metadatas[name] +} + +// isCacheMiss checks if the corresponding cache entry for a poolable router has already existed. +// False returns when the cache is nil, or cache's pool is nil, or cache's invokers snapshot is nil, or the entry +// doesn't exist. +func isCacheMiss(cache *InvokerCache, key string) bool { + if cache == nil || cache.pools == nil || cache.invokers == nil || cache.pools[key] == nil { + return true + } + return false +} + +// isInvokersChanged compares new invokers on the right changes, compared with the old invokers on the left. +func isInvokersChanged(left []protocol.Invoker, right []protocol.Invoker) bool { + if len(right) != len(left) { + return true + } + + for _, r := range right { + found := false + rurl := r.GetUrl() + for _, l := range left { + lurl := l.GetUrl() + if common.GetCompareURLEqualFunc()(lurl, rurl, constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY) { + found = true + break + } + } + if !found { + return true + } + } + return false +} + // sortRouter Sort router instance by priority with stable algorithm func sortRouter(routers []router.PriorityRouter) { sort.Stable(byPriority(routers)) diff --git a/cluster/router/chain/chain_test.go b/cluster/router/chain/chain_test.go index dec03894ebc73e315c2bb161911bdc67235e1ebb..b21990b08c5b960c407a395cddf7ef7518ba5822 100644 --- a/cluster/router/chain/chain_test.go +++ b/cluster/router/chain/chain_test.go @@ -65,7 +65,6 @@ func TestNewRouterChain(t *testing.T) { assert.NoError(t, err) err = z.Create(path) assert.NoError(t, err) - testyml := `scope: application key: mock-app enabled: true @@ -81,7 +80,7 @@ conditions: defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) @@ -133,7 +132,7 @@ conditions: defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) chain, err := NewRouterChain(getConditionRouteUrl(applicationKey)) @@ -159,7 +158,7 @@ func TestRouterChainRoute(t *testing.T) { defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) chain, err := NewRouterChain(getConditionRouteUrl(applicationKey)) @@ -169,15 +168,15 @@ func TestRouterChainRoute(t *testing.T) { url := getConditionRouteUrl(applicationKey) assert.NotNil(t, url) - invokers := []protocol.Invoker{} + var invokers []protocol.Invoker dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) - chain.SetInvokers(invokers) + chain.buildCache() targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} - finalInvokers := chain.Route(invokers, &targetURL, inv) + finalInvokers := chain.Route(targetURL, inv) assert.Equal(t, 1, len(finalInvokers)) } @@ -203,20 +202,22 @@ conditions: defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) chain, err := NewRouterChain(getConditionRouteUrl(applicationKey)) assert.Nil(t, err) assert.Equal(t, 2, len(chain.routers)) - invokers := []protocol.Invoker{} + var invokers []protocol.Invoker dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + chain.SetInvokers(invokers) + chain.buildCache() targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} - finalInvokers := chain.Route(invokers, &targetURL, inv) + finalInvokers := chain.Route(targetURL, inv) assert.Equal(t, 0, len(finalInvokers)) } @@ -227,7 +228,7 @@ func TestRouterChainRouteNoRoute(t *testing.T) { defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, localIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(zk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) chain, err := NewRouterChain(getConditionNoRouteUrl(applicationKey)) @@ -236,13 +237,16 @@ func TestRouterChainRouteNoRoute(t *testing.T) { url := getConditionRouteUrl(applicationKey) assert.NotNil(t, url) - invokers := []protocol.Invoker{} + + var invokers []protocol.Invoker dubboURL, _ := common.NewURL(fmt.Sprintf(dubboForamt, test1234IP, port20000)) invokers = append(invokers, protocol.NewBaseInvoker(dubboURL)) + chain.SetInvokers(invokers) + chain.buildCache() targetURL, _ := common.NewURL(fmt.Sprintf(consumerFormat, test1111IP)) inv := &invocation.RPCInvocation{} - finalInvokers := chain.Route(invokers, &targetURL, inv) + finalInvokers := chain.Route(targetURL, inv) assert.Equal(t, 0, len(finalInvokers)) } @@ -253,7 +257,7 @@ func getConditionNoRouteUrl(applicationKey string) *common.URL { url.AddParam(forceField, forceValue) rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host != 1.2.3.4")) url.AddParam(constant.RULE_KEY, rule) - return &url + return url } func getConditionRouteUrl(applicationKey string) *common.URL { @@ -262,12 +266,12 @@ func getConditionRouteUrl(applicationKey string) *common.URL { url.AddParam(forceField, forceValue) rule := base64.URLEncoding.EncodeToString([]byte("host = 1.1.1.1 => host = 1.2.3.4")) url.AddParam(constant.RULE_KEY, rule) - return &url + return url } func getRouteUrl(applicationKey string) *common.URL { url, _ := common.NewURL(fmt.Sprintf(anyUrlFormat, test0000IP)) url.AddParam(applicationField, applicationKey) url.AddParam(forceField, forceValue) - return &url + return url } diff --git a/cluster/router/chain/invoker_cache.go b/cluster/router/chain/invoker_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..43cdfa50670f5f09666dbea10a2837d58cafd1b0 --- /dev/null +++ b/cluster/router/chain/invoker_cache.go @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package chain + +import ( + "github.com/RoaringBitmap/roaring" +) + +import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" + "github.com/apache/dubbo-go/protocol" +) + +// Cache caches all addresses relevant info for a snapshot of received invokers. It keeps a snapshot of the received +// address list, and also keeps address pools and address metadata from routers based on the same address snapshot, if +// the router implements Poolable. +type InvokerCache struct { + // The snapshot of invokers + invokers []protocol.Invoker + + // The bitmap representation for invokers snapshot + bitmap *roaring.Bitmap + + // Address pool from routers which implement Poolable + pools map[string]router.AddrPool + + // Address metadata from routers which implement Poolable + metadatas map[string]router.AddrMetadata +} + +// BuildCache builds address cache from the given invokers. +func BuildCache(invokers []protocol.Invoker) *InvokerCache { + return &InvokerCache{ + invokers: invokers, + bitmap: utils.ToBitmap(invokers), + pools: make(map[string]router.AddrPool, 8), + metadatas: make(map[string]router.AddrMetadata, 8), + } +} + +// GetInvokers get invokers snapshot. +func (c *InvokerCache) GetInvokers() []protocol.Invoker { + return c.invokers +} + +// FindAddrPool finds address pool for a poolable router. +func (c *InvokerCache) FindAddrPool(p router.Poolable) router.AddrPool { + return c.pools[p.Name()] +} + +// FindAddrMeta finds address metadata for a poolable router. +func (c *InvokerCache) FindAddrMeta(p router.Poolable) router.AddrMetadata { + return c.metadatas[p.Name()] +} + +// SetAddrPool sets address pool for a poolable router, for unit test only +func (c *InvokerCache) SetAddrPool(name string, pool router.AddrPool) { + c.pools[name] = pool +} + +// SetAddrMeta sets address metadata for a poolable router, for unit test only +func (c *InvokerCache) SetAddrMeta(name string, meta router.AddrMetadata) { + c.metadatas[name] = meta +} diff --git a/cluster/router/condition/app_router_test.go b/cluster/router/condition/app_router_test.go index cce96b12c95a691e828d91ba3d0629ddb6421954..879abc5cc8b607ee9245ce632800b056ff740cc5 100644 --- a/cluster/router/condition/app_router_test.go +++ b/cluster/router/condition/app_router_test.go @@ -71,7 +71,7 @@ conditions: defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) @@ -119,7 +119,7 @@ conditions: defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) @@ -158,7 +158,7 @@ conditions: defer z.Close() zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, ts.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) assert.Nil(t, err) @@ -194,5 +194,5 @@ func getAppRouteURL(applicationKey string) *common.URL { url, _ := common.NewURL(fmt.Sprintf(conditionFormat, constant.ANYHOST_VALUE)) url.AddParam("application", applicationKey) url.AddParam("force", "true") - return &url + return url } diff --git a/cluster/router/condition/factory_test.go b/cluster/router/condition/factory_test.go index 0f61b39fc71af3aaeffc731974a0fa997503693e..c916588eeeef68b796c35ee6b3b175e8de859863 100644 --- a/cluster/router/condition/factory_test.go +++ b/cluster/router/condition/factory_test.go @@ -26,12 +26,14 @@ import ( ) import ( - "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" @@ -50,13 +52,13 @@ const ( ) type MockInvoker struct { - url common.URL + url *common.URL available bool destroyed bool successCount int } -func NewMockInvoker(url common.URL, successCount int) *MockInvoker { +func NewMockInvoker(url *common.URL, successCount int) *MockInvoker { return &MockInvoker{ url: url, available: true, @@ -65,7 +67,7 @@ func NewMockInvoker(url common.URL, successCount int) *MockInvoker { } } -func (bi *MockInvoker) GetUrl() common.URL { +func (bi *MockInvoker) GetUrl() *common.URL { return bi.url } @@ -73,20 +75,20 @@ func getRouteUrl(rule string) *common.URL { url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE)) url.AddParam("rule", rule) url.AddParam("force", "true") - return &url + return url } func getRouteUrlWithForce(rule, force string) *common.URL { url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE)) url.AddParam("rule", rule) url.AddParam("force", force) - return &url + return url } func getRouteUrlWithNoForce(rule string) *common.URL { url, _ := common.NewURL(fmt.Sprintf(factoryUrlFormat, constant.ANYHOST_VALUE)) url.AddParam("rule", rule) - return &url + return url } func (bi *MockInvoker) IsAvailable() bool { @@ -132,36 +134,36 @@ func TestRoute_matchWhen(t *testing.T) { rule := base64.URLEncoding.EncodeToString([]byte("=> host = 1.2.3.4")) router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) cUrl, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, factory1111Ip)) - matchWhen := router.(*ConditionRouter).MatchWhen(&cUrl, inv) + matchWhen := router.(*ConditionRouter).MatchWhen(cUrl, inv) assert.Equal(t, true, matchWhen) rule1 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) router1, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule1)) - matchWhen1 := router1.(*ConditionRouter).MatchWhen(&cUrl, inv) + matchWhen1 := router1.(*ConditionRouter).MatchWhen(cUrl, inv) assert.Equal(t, true, matchWhen1) rule2 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.1,3.3.3.3 & host !=1.1.1.1 => host = 1.2.3.4")) router2, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule2)) - matchWhen2 := router2.(*ConditionRouter).MatchWhen(&cUrl, inv) + matchWhen2 := router2.(*ConditionRouter).MatchWhen(cUrl, inv) assert.Equal(t, false, matchWhen2) rule3 := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.4 & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) router3, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule3)) - matchWhen3 := router3.(*ConditionRouter).MatchWhen(&cUrl, inv) + matchWhen3 := router3.(*ConditionRouter).MatchWhen(cUrl, inv) assert.Equal(t, true, matchWhen3) rule4 := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) router4, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule4)) - matchWhen4 := router4.(*ConditionRouter).MatchWhen(&cUrl, inv) + matchWhen4 := router4.(*ConditionRouter).MatchWhen(cUrl, inv) assert.Equal(t, true, matchWhen4) rule5 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.1 => host = 1.2.3.4")) router5, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule5)) - matchWhen5 := router5.(*ConditionRouter).MatchWhen(&cUrl, inv) + matchWhen5 := router5.(*ConditionRouter).MatchWhen(cUrl, inv) assert.Equal(t, false, matchWhen5) rule6 := base64.URLEncoding.EncodeToString([]byte("host = 2.2.2.2,1.1.1.*,3.3.3.3 & host != 1.1.1.2 => host = 1.2.3.4")) router6, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule6)) - matchWhen6 := router6.(*ConditionRouter).MatchWhen(&cUrl, inv) + matchWhen6 := router6.(*ConditionRouter).MatchWhen(cUrl, inv) assert.Equal(t, true, matchWhen6) } func TestRoute_matchFilter(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() t.Logf("The local ip is %s", localIP) url1, _ := common.NewURL("dubbo://10.20.3.3:20880/com.foo.BarService?default.serialization=fastjson") url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) @@ -180,70 +182,70 @@ func TestRoute_matchFilter(t *testing.T) { router5, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule5)) router6, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule6)) cUrl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - fileredInvokers1 := router1.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers2 := router2.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers3 := router3.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers4 := router4.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers5 := router5.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - fileredInvokers6 := router6.Route(invokers, &cUrl, &invocation.RPCInvocation{}) - assert.Equal(t, 1, len(fileredInvokers1)) - assert.Equal(t, 0, len(fileredInvokers2)) - assert.Equal(t, 0, len(fileredInvokers3)) - assert.Equal(t, 1, len(fileredInvokers4)) - assert.Equal(t, 2, len(fileredInvokers5)) - assert.Equal(t, 1, len(fileredInvokers6)) + ret1 := router1.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), cUrl, &invocation.RPCInvocation{}) + ret2 := router2.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), cUrl, &invocation.RPCInvocation{}) + ret3 := router3.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), cUrl, &invocation.RPCInvocation{}) + ret4 := router4.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), cUrl, &invocation.RPCInvocation{}) + ret5 := router5.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), cUrl, &invocation.RPCInvocation{}) + ret6 := router6.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), cUrl, &invocation.RPCInvocation{}) + assert.Equal(t, 1, len(ret1.ToArray())) + assert.Equal(t, 0, len(ret2.ToArray())) + assert.Equal(t, 0, len(ret3.ToArray())) + assert.Equal(t, 1, len(ret4.ToArray())) + assert.Equal(t, 2, len(ret5.ToArray())) + assert.Equal(t, 1, len(ret6.ToArray())) } func TestRoute_methodRoute(t *testing.T) { inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("getFoo"), invocation.WithParameterTypes([]reflect.Type{}), invocation.WithArguments([]interface{}{})) rule := base64.URLEncoding.EncodeToString([]byte("host !=4.4.4.* & host = 2.2.2.2,1.1.1.1,3.3.3.3 => host = 1.2.3.4")) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) url, _ := common.NewURL("consumer://1.1.1.1/com.foo.BarService?methods=setFoo,getFoo,findFoo") - matchWhen := router.(*ConditionRouter).MatchWhen(&url, inv) + matchWhen := r.(*ConditionRouter).MatchWhen(url, inv) assert.Equal(t, true, matchWhen) url1, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) - matchWhen = router.(*ConditionRouter).MatchWhen(&url1, inv) + matchWhen = r.(*ConditionRouter).MatchWhen(url1, inv) assert.Equal(t, true, matchWhen) url2, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) rule2 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host!=1.1.1.1 => host = 1.2.3.4")) router2, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule2)) - matchWhen = router2.(*ConditionRouter).MatchWhen(&url2, inv) + matchWhen = router2.(*ConditionRouter).MatchWhen(url2, inv) assert.Equal(t, false, matchWhen) url3, _ := common.NewURL(fmt.Sprintf(factoryConsumerMethodFormat, factory1111Ip)) rule3 := base64.URLEncoding.EncodeToString([]byte("methods=getFoo & host=1.1.1.1 => host = 1.2.3.4")) router3, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule3)) - matchWhen = router3.(*ConditionRouter).MatchWhen(&url3, inv) + matchWhen = router3.(*ConditionRouter).MatchWhen(url3, inv) assert.Equal(t, true, matchWhen) } func TestRoute_ReturnFalse(t *testing.T) { url, _ := common.NewURL("") - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => false")) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 0, len(fileredInvokers)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, 0, len(ret.ToArray())) } func TestRoute_ReturnEmpty(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() url, _ := common.NewURL("") invokers := []protocol.Invoker{NewMockInvoker(url, 1), NewMockInvoker(url, 2), NewMockInvoker(url, 3)} inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => ")) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 0, len(fileredInvokers)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, 0, len(ret.ToArray())) } func TestRoute_ReturnAll(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() urlString := "dubbo://" + localIP + "/com.foo.BarService" dubboURL, _ := common.NewURL(urlString) mockInvoker1 := NewMockInvoker(dubboURL, 1) @@ -253,13 +255,13 @@ func TestRoute_ReturnAll(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, invokers, fileredInvokers) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, len(invokers), len(ret.ToArray())) } func TestRoute_HostFilter(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() url1, _ := common.NewURL(factory333URL) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) @@ -270,15 +272,15 @@ func TestRoute_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_Empty_HostFilter(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() url1, _ := common.NewURL(factory333URL) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) @@ -289,15 +291,15 @@ func TestRoute_Empty_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(" => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_False_HostFilter(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() url1, _ := common.NewURL(factory333URL) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) @@ -308,15 +310,15 @@ func TestRoute_False_HostFilter(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("true => " + " host = " + localIP)) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_Placeholder(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() url1, _ := common.NewURL(factory333URL) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) @@ -327,15 +329,15 @@ func TestRoute_Placeholder(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte("host = " + localIP + " => " + " host = $host")) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 2, len(fileredInvokers)) - assert.Equal(t, invoker2, fileredInvokers[0]) - assert.Equal(t, invoker3, fileredInvokers[1]) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrl(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, 2, len(ret.ToArray())) + assert.Equal(t, invoker2, invokers[ret.ToArray()[0]]) + assert.Equal(t, invoker3, invokers[ret.ToArray()[1]]) } func TestRoute_NoForce(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() url1, _ := common.NewURL(factory333URL) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) @@ -346,13 +348,13 @@ func TestRoute_NoForce(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP))) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithNoForce(rule)) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, invokers, fileredInvokers) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithNoForce(rule)) + ret := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, len(invokers), len(ret.ToArray())) } func TestRoute_Force(t *testing.T) { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() url1, _ := common.NewURL(factory333URL) url2, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) url3, _ := common.NewURL(fmt.Sprintf(factoryDubboFormat, localIP)) @@ -363,9 +365,9 @@ func TestRoute_Force(t *testing.T) { inv := &invocation.RPCInvocation{} rule := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf(factoryHostIp1234Format, localIP))) curl, _ := common.NewURL(fmt.Sprintf(factoryConsumerFormat, localIP)) - router, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithForce(rule, "true")) - fileredInvokers := router.(*ConditionRouter).Route(invokers, &curl, inv) - assert.Equal(t, 0, len(fileredInvokers)) + r, _ := newConditionRouterFactory().NewPriorityRouter(getRouteUrlWithForce(rule, "true")) + fileredInvokers := r.Route(utils.ToBitmap(invokers), setUpAddrCache(invokers), curl, inv) + assert.Equal(t, 0, len(fileredInvokers.ToArray())) } func TestNewConditionRouterFactory(t *testing.T) { @@ -377,3 +379,7 @@ func TestNewAppRouterFactory(t *testing.T) { factory := newAppRouterFactory() assert.NotNil(t, factory) } + +func setUpAddrCache(addrs []protocol.Invoker) router.Cache { + return chain.BuildCache(addrs) +} diff --git a/cluster/router/condition/file.go b/cluster/router/condition/file.go index 996db7443faff88d3baa1a2363f98fa62623d121..a97d9cb83b9c176a353a3851c5590df91eb22730 100644 --- a/cluster/router/condition/file.go +++ b/cluster/router/condition/file.go @@ -39,7 +39,7 @@ import ( type FileConditionRouter struct { listenableRouter parseOnce sync.Once - url common.URL + url *common.URL } // NewFileConditionRouter Create file condition router instance with content ( from config file) @@ -60,11 +60,11 @@ func NewFileConditionRouter(content []byte) (*FileConditionRouter, error) { } // URL Return URL in file condition router n -func (f *FileConditionRouter) URL() common.URL { +func (f *FileConditionRouter) URL() *common.URL { f.parseOnce.Do(func() { routerRule := f.routerRule rule := parseCondition(routerRule.Conditions) - f.url = *common.NewURLWithOptions( + f.url = common.NewURLWithOptions( common.WithProtocol(constant.CONDITION_ROUTE_PROTOCOL), common.WithIp(constant.ANYHOST_VALUE), common.WithParams(url.Values{}), diff --git a/cluster/router/condition/listenable_router.go b/cluster/router/condition/listenable_router.go index 7f4f14a8e47173253e2e5b7f4eed5db2bed64958..0b47310dbfe9987a593bc2d0d949a76f08052114 100644 --- a/cluster/router/condition/listenable_router.go +++ b/cluster/router/condition/listenable_router.go @@ -22,10 +22,12 @@ import ( ) import ( + "github.com/RoaringBitmap/roaring" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" @@ -64,14 +66,14 @@ func newListenableRouter(url *common.URL, ruleKey string) (*AppRouter, error) { l.priority = listenableRouterDefaultPriority routerKey := ruleKey + constant.ConditionRouterRuleSuffix - //add listener + // add listener dynamicConfiguration := config.GetEnvInstance().GetDynamicConfiguration() if dynamicConfiguration == nil { return nil, perrors.Errorf("Get dynamicConfiguration fail, dynamicConfiguration is nil, init config center plugin please") } dynamicConfiguration.AddListener(routerKey, l) - //get rule + // get rule rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP)) if len(rule) == 0 || err != nil { return nil, perrors.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) @@ -129,13 +131,13 @@ func (l *listenableRouter) generateConditions(rule *RouterRule) { } // Route Determine the target invokers list. -func (l *listenableRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - if len(invokers) == 0 || len(l.conditionRouters) == 0 { +func (l *listenableRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + if invokers.IsEmpty() || len(l.conditionRouters) == 0 { return invokers } - //We will check enabled status inside each router. + // We will check enabled status inside each router. for _, r := range l.conditionRouters { - invokers = r.Route(invokers, url, invocation) + invokers = r.Route(invokers, cache, url, invocation) } return invokers } @@ -146,6 +148,6 @@ func (l *listenableRouter) Priority() int64 { } // URL Return URL in listenable router -func (l *listenableRouter) URL() common.URL { - return *l.url +func (l *listenableRouter) URL() *common.URL { + return l.url } diff --git a/cluster/router/condition/router.go b/cluster/router/condition/router.go index 751b5a7111655577566c561614d39093485130cd..2fc33072d33517810f0c9ab3b8351d7bdcfea8b4 100644 --- a/cluster/router/condition/router.go +++ b/cluster/router/condition/router.go @@ -23,20 +23,22 @@ import ( ) import ( + "github.com/RoaringBitmap/roaring" + "github.com/dubbogo/gost/container/set" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/protocol" - "github.com/dubbogo/gost/container/set" - "github.com/dubbogo/gost/net" ) const ( - //pattern route pattern regex + // pattern route pattern regex pattern = `([&!=,]*)\\s*([^&!=,\\s]+)` ) @@ -136,8 +138,8 @@ func (c *ConditionRouter) Priority() int64 { } // URL Return URL in condition router -func (c *ConditionRouter) URL() common.URL { - return *c.url +func (c *ConditionRouter) URL() *common.URL { + return c.url } // Enabled Return is condition router is enabled @@ -148,36 +150,44 @@ func (c *ConditionRouter) Enabled() bool { } // Route Determine the target invokers list. -func (c *ConditionRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { +func (c *ConditionRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if !c.Enabled() { return invokers } - if len(invokers) == 0 { + + if invokers.IsEmpty() { return invokers } + isMatchWhen := c.MatchWhen(url, invocation) if !isMatchWhen { return invokers } - var result []protocol.Invoker + if len(c.ThenCondition) == 0 { - return result + return utils.EmptyAddr } - for _, invoker := range invokers { + + result := roaring.NewBitmap() + for iter := invokers.Iterator(); iter.HasNext(); { + index := iter.Next() + invoker := cache.GetInvokers()[index] invokerUrl := invoker.GetUrl() - isMatchThen := c.MatchThen(&invokerUrl, url) + isMatchThen := c.MatchThen(invokerUrl, url) if isMatchThen { - result = append(result, invoker) + result.Add(index) } } - if len(result) > 0 { + + if !result.IsEmpty() { return result } else if c.Force { rule, _ := url.GetParamAndDecoded(constant.RULE_KEY) - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() logger.Warnf("The route result is empty and force execute. consumer: %s, service: %s, router: %s", localIP, url.Service(), rule) return result } + return invokers } diff --git a/cluster/router/condition/router_rule_test.go b/cluster/router/condition/router_rule_test.go index 369b14f08a6a650b36fe63a2d890c04fe7b892a5..192e5282f6f50c31b1106a5fc547a1616405a22a 100644 --- a/cluster/router/condition/router_rule_test.go +++ b/cluster/router/condition/router_rule_test.go @@ -80,5 +80,5 @@ conditions: func TestIsMatchGlobPattern(t *testing.T) { url, _ := common.NewURL("dubbo://localhost:8080/Foo?key=v*e") - assert.Equal(t, true, isMatchGlobalPattern("$key", "value", &url)) + assert.Equal(t, true, isMatchGlobalPattern("$key", "value", url)) } diff --git a/cluster/router/condition/router_test.go b/cluster/router/condition/router_test.go index c27a1d9552a331d7e67af0eb3d444c480758891e..3d33ca275828b31ed480926d486c5844dac771c6 100644 --- a/cluster/router/condition/router_test.go +++ b/cluster/router/condition/router_test.go @@ -59,7 +59,7 @@ func TestParseRule(t *testing.T) { func TestNewConditionRouter(t *testing.T) { url, _ := common.NewURL(`condition://0.0.0.0:?application=mock-app&category=routers&force=true&priority=1&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D`) - router, err := NewConditionRouter(&url) + router, err := NewConditionRouter(url) assert.Nil(t, err) assert.Equal(t, true, router.Enabled()) assert.Equal(t, true, router.Force) @@ -73,16 +73,16 @@ func TestNewConditionRouter(t *testing.T) { assert.Error(t, err) url, _ = common.NewURL(`condition://0.0.0.0:?application=mock-app&category=routers&force=true&priority=1&router=condition&rule=YSAmT4gYiAmIGQ%3D`) - router, err = NewConditionRouter(&url) + router, err = NewConditionRouter(url) assert.Error(t, err) url, _ = common.NewURL(`condition://0.0.0.0:?application=mock-app&category=routers&force=true&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D`) - router, err = NewConditionRouter(&url) + router, err = NewConditionRouter(url) assert.Nil(t, err) assert.Equal(t, int64(150), router.Priority()) url, _ = common.NewURL(`condition://0.0.0.0:?category=routers&force=true&interface=mock-service&router=condition&rule=YSAmIGMgPT4gYiAmIGQ%3D`) - router, err = NewConditionRouter(&url) + router, err = NewConditionRouter(url) assert.Nil(t, err) assert.Equal(t, int64(140), router.Priority()) } diff --git a/cluster/router/healthcheck/default_health_check.go b/cluster/router/healthcheck/default_health_check.go index c693b86ecdab7b32936185fe4bd614bd0f83fbeb..378463be56a43f4934d23c340d81d66bfdeeea4c 100644 --- a/cluster/router/healthcheck/default_health_check.go +++ b/cluster/router/healthcheck/default_health_check.go @@ -31,7 +31,7 @@ import ( ) func init() { - extension.SethealthChecker(constant.DEFAULT_HEALTH_CHECKER, NewDefaultHealthChecker) + extension.SetHealthChecker(constant.DEFAULT_HEALTH_CHECKER, NewDefaultHealthChecker) } // DefaultHealthChecker is the default implementation of HealthChecker, which determines the health status of @@ -48,6 +48,10 @@ type DefaultHealthChecker struct { // IsHealthy evaluates the healthy state on the given Invoker based on the number of successive bad request // and the current active request func (c *DefaultHealthChecker) IsHealthy(invoker protocol.Invoker) bool { + if !invoker.IsAvailable() { + return false + } + urlStatus := protocol.GetURLStatus(invoker.GetUrl()) if c.isCircuitBreakerTripped(urlStatus) || urlStatus.GetActive() > c.GetOutStandingRequestCountLimit() { logger.Debugf("Invoker [%s] is currently in circuitbreaker tripped state", invoker.GetUrl().Key()) @@ -85,7 +89,7 @@ func (c *DefaultHealthChecker) getCircuitBreakerSleepWindowTime(status *protocol } else if diff > constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF { diff = constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF } - sleepWindow := (1 << diff) * c.GetCircuitTrippedTimeoutFactor() + sleepWindow := (1 << uint(diff)) * c.GetCircuitTrippedTimeoutFactor() if sleepWindow > constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS { sleepWindow = constant.MAX_CIRCUIT_TRIPPED_TIMEOUT_IN_MS } @@ -110,8 +114,8 @@ func (c *DefaultHealthChecker) GetOutStandingRequestCountLimit() int32 { // NewDefaultHealthChecker constructs a new DefaultHealthChecker based on the url func NewDefaultHealthChecker(url *common.URL) router.HealthChecker { return &DefaultHealthChecker{ - outStandingRequestConutLimit: int32(url.GetParamInt(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, math.MaxInt32)), - requestSuccessiveFailureThreshold: int32(url.GetParamInt(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF)), - circuitTrippedTimeoutFactor: int32(url.GetParamInt(constant.CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY, constant.DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR)), + outStandingRequestConutLimit: url.GetParamInt32(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, math.MaxInt32), + requestSuccessiveFailureThreshold: url.GetParamInt32(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF), + circuitTrippedTimeoutFactor: url.GetParamInt32(constant.CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY, constant.DEFAULT_CIRCUIT_TRIPPED_TIMEOUT_FACTOR), } } diff --git a/cluster/router/healthcheck/default_health_check_test.go b/cluster/router/healthcheck/default_health_check_test.go index 5d35ae8e486e3f7b29b2a68a3864ef806a1053c7..39827c5f050a1a5ac9524345b59e37ec23efb90f 100644 --- a/cluster/router/healthcheck/default_health_check_test.go +++ b/cluster/router/healthcheck/default_health_check_test.go @@ -43,7 +43,7 @@ const ( func TestDefaultHealthCheckerIsHealthy(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) - hc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + hc := NewDefaultHealthChecker(url).(*DefaultHealthChecker) invoker := NewMockInvoker(url) healthy := hc.IsHealthy(invoker) assert.True(t, healthy) @@ -54,7 +54,7 @@ func TestDefaultHealthCheckerIsHealthy(t *testing.T) { for i := 0; i < 11; i++ { request(url, healthCheckMethodTest, 0, true, false) } - hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + hc = NewDefaultHealthChecker(url).(*DefaultHealthChecker) healthy = hc.IsHealthy(invoker) // the outgoing request is more than OUTSTANDING_REQUEST_COUNT_LIMIT, go to unhealthy assert.False(t, hc.IsHealthy(invoker)) @@ -65,7 +65,7 @@ func TestDefaultHealthCheckerIsHealthy(t *testing.T) { } url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000") - hc = NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + hc = NewDefaultHealthChecker(url).(*DefaultHealthChecker) healthy = hc.IsHealthy(invoker) assert.False(t, hc.IsHealthy(invoker)) @@ -78,7 +78,7 @@ func TestDefaultHealthCheckerIsHealthy(t *testing.T) { func TestDefaultHealthCheckerGetCircuitBreakerSleepWindowTime(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) - defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + defaultHc := NewDefaultHealthChecker(url).(*DefaultHealthChecker) // Increase the number of failed requests for i := 0; i < 100; i++ { request(url, healthCheckMethodTest, 1, false, false) @@ -88,7 +88,7 @@ func TestDefaultHealthCheckerGetCircuitBreakerSleepWindowTime(t *testing.T) { // Adjust the threshold size to 1000 url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "1000") - sleepWindowTime = NewDefaultHealthChecker(&url).(*DefaultHealthChecker).getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url)) + sleepWindowTime = NewDefaultHealthChecker(url).(*DefaultHealthChecker).getCircuitBreakerSleepWindowTime(protocol.GetURLStatus(url)) assert.True(t, sleepWindowTime == 0) url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1011IP)) @@ -107,7 +107,7 @@ func TestDefaultHealthCheckerGetCircuitBreakerSleepWindowTime(t *testing.T) { func TestDefaultHealthCheckerGetCircuitBreakerTimeout(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) - defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + defaultHc := NewDefaultHealthChecker(url).(*DefaultHealthChecker) timeout := defaultHc.getCircuitBreakerTimeout(protocol.GetURLStatus(url)) assert.True(t, timeout == 0) url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1011IP)) @@ -126,7 +126,7 @@ func TestDefaultHealthCheckerGetCircuitBreakerTimeout(t *testing.T) { func TestDefaultHealthCheckerIsCircuitBreakerTripped(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) - defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + defaultHc := NewDefaultHealthChecker(url).(*DefaultHealthChecker) status := protocol.GetURLStatus(url) tripped := defaultHc.isCircuitBreakerTripped(status) assert.False(t, tripped) @@ -142,7 +142,7 @@ func TestDefaultHealthCheckerIsCircuitBreakerTripped(t *testing.T) { func TestNewDefaultHealthChecker(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) - defaultHc := NewDefaultHealthChecker(&url).(*DefaultHealthChecker) + defaultHc := NewDefaultHealthChecker(url).(*DefaultHealthChecker) assert.NotNil(t, defaultHc) assert.Equal(t, defaultHc.outStandingRequestConutLimit, int32(math.MaxInt32)) assert.Equal(t, defaultHc.requestSuccessiveFailureThreshold, int32(constant.DEFAULT_SUCCESSIVE_FAILED_REQUEST_MAX_DIFF)) @@ -150,13 +150,13 @@ func TestNewDefaultHealthChecker(t *testing.T) { url1, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) url1.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "10") url1.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") - nondefaultHc := NewDefaultHealthChecker(&url1).(*DefaultHealthChecker) + nondefaultHc := NewDefaultHealthChecker(url1).(*DefaultHealthChecker) assert.NotNil(t, nondefaultHc) assert.Equal(t, nondefaultHc.outStandingRequestConutLimit, int32(10)) assert.Equal(t, nondefaultHc.requestSuccessiveFailureThreshold, int32(10)) } -func request(url common.URL, method string, elapsed int64, active, succeeded bool) { +func request(url *common.URL, method string, elapsed int64, active, succeeded bool) { protocol.BeginCount(url, method) if !active { protocol.EndCount(url, method, elapsed, succeeded) diff --git a/cluster/router/healthcheck/factory_test.go b/cluster/router/healthcheck/factory_test.go index e80fd4c4d38dbb1c0f2b14ba1e22971249bc54b6..1e736832cecf07d523960fce8eebcc9248cee0a9 100644 --- a/cluster/router/healthcheck/factory_test.go +++ b/cluster/router/healthcheck/factory_test.go @@ -33,18 +33,18 @@ import ( // nolint type MockInvoker struct { - url common.URL + url *common.URL } // nolint -func NewMockInvoker(url common.URL) *MockInvoker { +func NewMockInvoker(url *common.URL) *MockInvoker { return &MockInvoker{ url: url, } } // nolint -func (bi *MockInvoker) GetUrl() common.URL { +func (bi *MockInvoker) GetUrl() *common.URL { return bi.url } diff --git a/cluster/router/healthcheck/health_check_route.go b/cluster/router/healthcheck/health_check_route.go index ee42e47e3b26c9a1976b4599d3464d752b615e0a..1a878af2127d2c5b979cfdb55ff1c1ec08463fe7 100644 --- a/cluster/router/healthcheck/health_check_route.go +++ b/cluster/router/healthcheck/health_check_route.go @@ -17,8 +17,13 @@ package healthcheck +import ( + "github.com/RoaringBitmap/roaring" +) + import ( "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -28,6 +33,8 @@ import ( const ( HEALTH_ROUTE_ENABLED_KEY = "health.route.enabled" + healthy = "healthy" + name = "health-check-router" ) // HealthCheckRouter provides a health-first routing mechanism through HealthChecker @@ -51,33 +58,56 @@ func NewHealthCheckRouter(url *common.URL) (router.PriorityRouter, error) { } // Route gets a list of healthy invoker -func (r *HealthCheckRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { +func (r *HealthCheckRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { if !r.enabled { return invokers } - healthyInvokers := make([]protocol.Invoker, 0, len(invokers)) + + addrPool := cache.FindAddrPool(r) // Add healthy invoker to the list - for _, invoker := range invokers { - if r.checker.IsHealthy(invoker) { - healthyInvokers = append(healthyInvokers, invoker) - } - } - // If all Invoke are considered unhealthy, downgrade to all inovker - if len(healthyInvokers) == 0 { + healthyInvokers := utils.JoinIfNotEqual(addrPool[healthy], invokers) + // If all invokers are considered unhealthy, downgrade to all invoker + if healthyInvokers.IsEmpty() { logger.Warnf(" Now all invokers are unhealthy, so downgraded to all! Service: [%s]", url.ServiceKey()) return invokers } return healthyInvokers } +// Pool separates healthy invokers from others. +func (r *HealthCheckRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { + if !r.enabled { + return nil, nil + } + + rb := make(router.AddrPool, 8) + rb[healthy] = roaring.NewBitmap() + for i, invoker := range invokers { + if r.checker.IsHealthy(invoker) { + rb[healthy].Add(uint32(i)) + } + } + + return rb, nil +} + +// ShouldPool will always return true to make sure healthy check constantly. +func (r *HealthCheckRouter) ShouldPool() bool { + return r.enabled +} + +func (r *HealthCheckRouter) Name() string { + return name +} + // Priority func (r *HealthCheckRouter) Priority() int64 { return 0 } // URL Return URL in router -func (r *HealthCheckRouter) URL() common.URL { - return *r.url +func (r *HealthCheckRouter) URL() *common.URL { + return r.url } // HealthyChecker returns the HealthChecker bound to this HealthCheckRouter diff --git a/cluster/router/healthcheck/health_check_route_test.go b/cluster/router/healthcheck/health_check_route_test.go index d5862fb884114bac0ea2ec9ee8926baac57d5ba6..0730f105b7e010311576fc30adf94c8ad69b9614 100644 --- a/cluster/router/healthcheck/health_check_route_test.go +++ b/cluster/router/healthcheck/health_check_route_test.go @@ -29,6 +29,9 @@ import ( ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" @@ -51,7 +54,7 @@ func TestHealthCheckRouterRoute(t *testing.T) { url1, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1010IP)) url2, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1011IP)) url3, _ := common.NewURL(fmt.Sprintf(healthCheckRouteUrlFormat, healthCheckRoute1012IP)) - hcr, _ := NewHealthCheckRouter(&consumerURL) + hcr, _ := NewHealthCheckRouter(consumerURL) var invokers []protocol.Invoker invoker1 := NewMockInvoker(url1) @@ -59,25 +62,25 @@ func TestHealthCheckRouterRoute(t *testing.T) { invoker3 := NewMockInvoker(url3) invokers = append(invokers, invoker1, invoker2, invoker3) inv := invocation.NewRPCInvocation(healthCheckRouteMethodNameTest, nil, nil) - res := hcr.Route(invokers, &consumerURL, inv) + res := hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), consumerURL, inv) // now all invokers are healthy - assert.True(t, len(res) == len(invokers)) + assert.True(t, len(res.ToArray()) == len(invokers)) for i := 0; i < 10; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) } - res = hcr.Route(invokers, &consumerURL, inv) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), consumerURL, inv) // invokers1 is unhealthy now - assert.True(t, len(res) == 2 && !contains(res, invoker1)) + assert.True(t, len(res.ToArray()) == 2 && !res.Contains(0)) for i := 0; i < 10; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) request(url2, healthCheckRouteMethodNameTest, 0, false, false) } - res = hcr.Route(invokers, &consumerURL, inv) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), consumerURL, inv) // only invokers3 is healthy now - assert.True(t, len(res) == 1 && !contains(res, invoker1) && !contains(res, invoker2)) + assert.True(t, len(res.ToArray()) == 1 && !res.Contains(0) && !res.Contains(1)) for i := 0; i < 10; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) @@ -85,46 +88,37 @@ func TestHealthCheckRouterRoute(t *testing.T) { request(url3, healthCheckRouteMethodNameTest, 0, false, false) } - res = hcr.Route(invokers, &consumerURL, inv) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), consumerURL, inv) // now all invokers are unhealthy, so downgraded to all - assert.True(t, len(res) == 3) + assert.True(t, len(res.ToArray()) == 3) // reset the invoker1 successive failed count, so invoker1 go to healthy request(url1, healthCheckRouteMethodNameTest, 0, false, true) - res = hcr.Route(invokers, &consumerURL, inv) - assert.True(t, contains(res, invoker1)) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), consumerURL, inv) + assert.True(t, res.Contains(0)) for i := 0; i < 6; i++ { request(url1, healthCheckRouteMethodNameTest, 0, false, false) } // now all invokers are unhealthy, so downgraded to all again - res = hcr.Route(invokers, &consumerURL, inv) - assert.True(t, len(res) == 3) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), consumerURL, inv) + assert.True(t, len(res.ToArray()) == 3) time.Sleep(time.Second * 2) // invoker1 go to healthy again after 2s - res = hcr.Route(invokers, &consumerURL, inv) - assert.True(t, contains(res, invoker1)) + res = hcr.Route(utils.ToBitmap(invokers), setUpAddrCache(hcr.(*HealthCheckRouter), invokers), consumerURL, inv) + assert.True(t, res.Contains(0)) } -func contains(invokers []protocol.Invoker, invoker protocol.Invoker) bool { - for _, e := range invokers { - if e == invoker { - return true - } - } - return false -} - func TestNewHealthCheckRouter(t *testing.T) { defer protocol.CleanAllStatus() url, _ := common.NewURL(fmt.Sprintf(healthCheckDubboUrlFormat, healthCheckDubbo1010IP)) - hcr, _ := NewHealthCheckRouter(&url) + hcr, _ := NewHealthCheckRouter(url) h := hcr.(*HealthCheckRouter) assert.Nil(t, h.checker) url.SetParam(HEALTH_ROUTE_ENABLED_KEY, "true") - hcr, _ = NewHealthCheckRouter(&url) + hcr, _ = NewHealthCheckRouter(url) h = hcr.(*HealthCheckRouter) assert.NotNil(t, h.checker) @@ -136,10 +130,18 @@ func TestNewHealthCheckRouter(t *testing.T) { url.SetParam(constant.CIRCUIT_TRIPPED_TIMEOUT_FACTOR_KEY, "500") url.SetParam(constant.SUCCESSIVE_FAILED_REQUEST_THRESHOLD_KEY, "10") url.SetParam(constant.OUTSTANDING_REQUEST_COUNT_LIMIT_KEY, "1000") - hcr, _ = NewHealthCheckRouter(&url) + hcr, _ = NewHealthCheckRouter(url) h = hcr.(*HealthCheckRouter) dhc = h.checker.(*DefaultHealthChecker) assert.Equal(t, dhc.outStandingRequestConutLimit, int32(1000)) assert.Equal(t, dhc.requestSuccessiveFailureThreshold, int32(10)) assert.Equal(t, dhc.circuitTrippedTimeoutFactor, int32(500)) } + +func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) router.Cache { + pool, info := r.Pool(addrs) + cache := chain.BuildCache(addrs) + cache.SetAddrMeta(r.Name(), info) + cache.SetAddrPool(r.Name(), pool) + return cache +} diff --git a/cluster/router/router.go b/cluster/router/router.go index 66603c1d4d0efedad3489712ecea91b43254fffd..8a19dcf8cc2540ab0a30b8b6de8521e9759012ee 100644 --- a/cluster/router/router.go +++ b/cluster/router/router.go @@ -17,6 +17,10 @@ package router +import ( + "github.com/RoaringBitmap/roaring" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/protocol" @@ -38,9 +42,10 @@ type FilePriorityRouterFactory interface { // Router type router interface { // Route Determine the target invokers list. - Route([]protocol.Invoker, *common.URL, protocol.Invocation) []protocol.Invoker + Route(*roaring.Bitmap, Cache, *common.URL, protocol.Invocation) *roaring.Bitmap + // URL Return URL in router - URL() common.URL + URL() *common.URL } // Router @@ -51,10 +56,38 @@ type PriorityRouter interface { Priority() int64 } -// NotifyRouter notify router use the invoker list. Invoker list may change from time to time. This method gives the router a -// chance to prepare before {@link Router#route(List, URL, Invocation)} gets called. -type NotifyRouter interface { - PriorityRouter - // Notify notify whenever addresses in registry change - Notify([]protocol.Invoker) +// Poolable caches address pool and address metadata for a router instance which will be used later in Router's Route. +type Poolable interface { + // Pool created address pool and address metadata from the invokers. + Pool([]protocol.Invoker) (AddrPool, AddrMetadata) + + // ShouldPool returns if it should pool. One typical scenario is a router rule changes, in this case, a pooling + // is necessary, even if the addresses not changed at all. + ShouldPool() bool + + // Name return the Poolable's name. + Name() string +} + +// AddrPool is an address pool, backed by a snapshot of address list, divided into categories. +type AddrPool map[string]*roaring.Bitmap + +// AddrMetadta is address metadata, collected from a snapshot of address list by a router, if it implements Poolable. +type AddrMetadata interface { + // Source indicates where the metadata comes from. + Source() string +} + +// Cache caches all addresses relevant info for a snapshot of received invokers. It keeps a snapshot of the received +// address list, and also keeps address pools and address metadata from routers based on the same address snapshot, if +// the router implements Poolable. +type Cache interface { + // GetInvokers returns the snapshot of received invokers. + GetInvokers() []protocol.Invoker + + // FindAddrPool returns address pool associated with the given Poolable instance. + FindAddrPool(Poolable) AddrPool + + // FindAddrMeta returns address metadata associated with the given Poolable instance. + FindAddrMeta(Poolable) AddrMetadata } diff --git a/cluster/router/tag/factory_test.go b/cluster/router/tag/factory_test.go index ee195820c123e1fc67a2c27cd12aaa544650b615..b350bb2a915ce9abae98ba46bda346fba7ad6b5b 100644 --- a/cluster/router/tag/factory_test.go +++ b/cluster/router/tag/factory_test.go @@ -39,7 +39,7 @@ func TestTagRouterFactoryNewRouter(t *testing.T) { u1, err := common.NewURL(fmt.Sprintf(factoryFormat, factoryLocalIP)) assert.Nil(t, err) factory := NewTagRouterFactory() - tagRouter, e := factory.NewPriorityRouter(&u1) + tagRouter, e := factory.NewPriorityRouter(u1) assert.Nil(t, e) assert.NotNil(t, tagRouter) } diff --git a/cluster/router/tag/file.go b/cluster/router/tag/file.go index 433abcb72eb6e201e64790af932e847d8faba8af..94daf1508eb7b3f4d8a8cacdbd6ed634be6852da 100644 --- a/cluster/router/tag/file.go +++ b/cluster/router/tag/file.go @@ -24,10 +24,12 @@ import ( ) import ( + "github.com/RoaringBitmap/roaring" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" @@ -50,13 +52,12 @@ func NewFileTagRouter(content []byte) (*FileTagRouter, error) { return nil, perrors.Errorf("yaml.Unmarshal() failed , error:%v", perrors.WithStack(err)) } fileRouter.routerRule = rule - url := fileRouter.URL() - fileRouter.router, err = NewTagRouter(&url) + fileRouter.router, err = NewTagRouter(fileRouter.URL()) return fileRouter, err } // URL Return URL in file tag router n -func (f *FileTagRouter) URL() common.URL { +func (f *FileTagRouter) URL() *common.URL { f.parseOnce.Do(func() { routerRule := f.routerRule f.url = common.NewURLWithOptions( @@ -66,7 +67,7 @@ func (f *FileTagRouter) URL() common.URL { common.WithParamsValue(constant.RouterPriority, strconv.Itoa(routerRule.Priority)), common.WithParamsValue(constant.ROUTER_KEY, constant.TAG_ROUTE_PROTOCOL)) }) - return *f.url + return f.url } // Priority Return Priority in listenable router @@ -74,9 +75,10 @@ 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 { +func (f *FileTagRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + if invokers.IsEmpty() { return invokers } - return f.Route(invokers, url, invocation) + // FIXME: I believe this is incorrect. + return f.Route(invokers, cache, url, invocation) } diff --git a/cluster/router/tag/tag_router.go b/cluster/router/tag/tag_router.go index a5f1dc13d9385fe6bc230e79337230e674b97e96..c7f53047c1e2beee4545a10302e9f307f06c33d8 100644 --- a/cluster/router/tag/tag_router.go +++ b/cluster/router/tag/tag_router.go @@ -18,17 +18,20 @@ package tag import ( - "net" "strconv" + "strings" + "sync" ) import ( + "github.com/RoaringBitmap/roaring" gxnet "github.com/dubbogo/gost/net" - "github.com/jinzhu/copier" perrors "github.com/pkg/errors" ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/config" "github.com/apache/dubbo-go/common/constant" @@ -38,6 +41,32 @@ import ( "github.com/apache/dubbo-go/remoting" ) +const ( + name = "tag-router" + staticPrefix = "static-" + dynamicPrefix = "dynamic-" +) + +// addrMetadata keeps snapshot data for app name, and some infos extracted from dynamic tag rule in order to make +// Route() method lock-free. +type addrMetadata struct { + // application name + application string + // is rule a runtime rule + ruleRuntime bool + // is rule a force rule + ruleForce bool + // is rule a valid rule + ruleValid bool + // is rule an enabled rule + ruleEnabled bool +} + +// Source indicates where the metadata comes from. +func (m *addrMetadata) Source() string { + return name +} + // tagRouter defines url, enable and the priority type tagRouter struct { url *common.URL @@ -45,6 +74,8 @@ type tagRouter struct { enabled bool priority int64 application string + ruleChanged bool + mutex sync.RWMutex } // NewTagRouter returns a tagRouter instance if url is not nil @@ -65,56 +96,79 @@ func (c *tagRouter) isEnabled() bool { } // Route gets a list of invoker -func (c *tagRouter) Route(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - var ( - result []protocol.Invoker - addresses []string - ) +func (c *tagRouter) Route(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + if !c.isEnabled() || invokers.IsEmpty() { + return invokers + } + + if shouldUseDynamicTag(cache.FindAddrMeta(c)) { + return c.routeWithDynamicTag(invokers, cache, url, invocation) + } + return c.routeWithStaticTag(invokers, cache, url, invocation) +} - if !c.isEnabled() || len(invokers) == 0 { +// routeWithStaticTag routes with static tag rule +func (c *tagRouter) routeWithStaticTag(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + tag := findTag(invocation, url) + if tag == "" { return invokers } - // Use static tags if dynamic tags are not set or invalid - if c.tagRouterRule == nil || !c.tagRouterRule.Valid || !c.tagRouterRule.Enabled { - return filterUsingStaticTag(invokers, url, invocation) + ret, _ := c.filterWithTag(invokers, cache, staticPrefix+tag) + if ret.IsEmpty() && !isForceUseTag(url, invocation) { + return invokers } - // since the rule can be changed by config center, we should copy one to use. - tagRouterRuleCopy := new(RouterRule) - _ = copier.Copy(tagRouterRuleCopy, c.tagRouterRule) - tag, ok := invocation.Attachments()[constant.Tagkey] - if !ok { - tag = url.GetParam(constant.Tagkey, "") + return ret +} + +// routeWithDynamicTag routes with dynamic tag rule +func (c *tagRouter) routeWithDynamicTag(invokers *roaring.Bitmap, cache router.Cache, url *common.URL, invocation protocol.Invocation) *roaring.Bitmap { + tag := findTag(invocation, url) + if tag == "" { + return c.filterNotInDynamicTag(invokers, cache) } - // if we are requesting for a Provider with a specific tag - if len(tag) > 0 { - return filterInvokersWithTag(invokers, url, invocation, *tagRouterRuleCopy, tag) + ret, ok := c.filterWithTag(invokers, cache, dynamicPrefix+tag) + if ok && (!ret.IsEmpty() || isTagRuleForce(cache.FindAddrMeta(c))) { + return ret } - // return all addresses in dynamic tag group. - addresses = tagRouterRuleCopy.getAddresses() - if len(addresses) > 0 { - filterAddressNotMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - return len(addresses) == 0 || !checkAddressMatch(addresses, url.Ip, url.Port) - } - result = filterInvoker(invokers, filterAddressNotMatches) - // 1. all addresses are in dynamic tag group, return empty list. - if len(result) == 0 { - return result + // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by + // dynamic tag group but force=false. check static tag + if ret.IsEmpty() { + ret, _ = c.filterWithTag(invokers, cache, staticPrefix+tag) + // If there's no tagged providers that can match the current tagged request. force.tag is set by default + // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. + if !ret.IsEmpty() || isForceUseTag(url, invocation) { + return ret } + return c.filterNotInDynamicTag(invokers, cache) } - // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the - // static tag group. - filter := func(invoker protocol.Invoker) bool { - localTag := invoker.GetUrl().GetParam(constant.Tagkey, "") - return len(localTag) == 0 || !(tagRouterRuleCopy.hasTag(localTag)) + return ret +} + +// filterWithTag filters incoming invokers with the given tag +func (c *tagRouter) filterWithTag(invokers *roaring.Bitmap, cache router.Cache, tag string) (*roaring.Bitmap, bool) { + if target, ok := cache.FindAddrPool(c)[tag]; ok { + return utils.JoinIfNotEqual(target, invokers), true } - return filterInvoker(result, filter) + return utils.EmptyAddr, false } +// filterNotInDynamicTag filters incoming invokers not applied to dynamic tag rule +func (c *tagRouter) filterNotInDynamicTag(invokers *roaring.Bitmap, cache router.Cache) *roaring.Bitmap { + // FAILOVER: return all Providers without any tags. + invokers = invokers.Clone() + for k, v := range cache.FindAddrPool(c) { + if strings.HasPrefix(k, dynamicPrefix) { + invokers.AndNot(v) + } + } + return invokers +} + +// Process parses dynamic tag rule func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { logger.Infof("Notification of tag rule, change type is:[%s] , raw rule is:[%v]", event.ConfigType, event.Value) if remoting.EventTypeDel == event.ConfigType { @@ -132,16 +186,52 @@ func (c *tagRouter) Process(event *config_center.ConfigChangeEvent) { logger.Errorf("Parse dynamic tag router rule fail,error:[%s] ", err) return } + + c.mutex.Lock() + defer c.mutex.Unlock() c.tagRouterRule = routerRule - return + c.ruleChanged = true +} + +// URL gets the url of tagRouter +func (c *tagRouter) URL() *common.URL { + return c.url } -func (c *tagRouter) Notify(invokers []protocol.Invoker) { - if len(invokers) == 0 { +// Priority gets the priority of tagRouter +func (c *tagRouter) Priority() int64 { + return c.priority +} + +// Pool divided invokers into different address pool by tag. +func (c *tagRouter) Pool(invokers []protocol.Invoker) (router.AddrPool, router.AddrMetadata) { + c.fetchRuleIfNecessary(invokers) + + rb := make(router.AddrPool, 8) + poolWithStaticTag(invokers, rb) + + c.mutex.Lock() + defer c.mutex.Unlock() + poolWithDynamicTag(invokers, c.tagRouterRule, rb) + c.ruleChanged = false + // create metadata in order to avoid lock in route() + meta := addrMetadata{application: c.application} + if c.tagRouterRule != nil { + meta.ruleForce = c.tagRouterRule.Force + meta.ruleEnabled = c.tagRouterRule.Enabled + meta.ruleValid = c.tagRouterRule.Valid + } + + return rb, &meta +} + +// fetchRuleIfNecessary fetches, parses rule and register listener for the further change +func (c *tagRouter) fetchRuleIfNecessary(invokers []protocol.Invoker) { + if invokers == nil || len(invokers) == 0 { return } - invoker := invokers[0] - url := invoker.GetUrl() + + url := invokers[0].GetUrl() providerApplication := url.GetParam(constant.RemoteApplicationKey, "") if len(providerApplication) == 0 { logger.Error("TagRouter must getConfig from or subscribe to a specific application, but the application " + @@ -156,11 +246,15 @@ func (c *tagRouter) Notify(invokers []protocol.Invoker) { if providerApplication != c.application { dynamicConfiguration.RemoveListener(c.application+constant.TagRouterRuleSuffix, c) + } else { + // if app name from URL is as same as the current app name, then it is safe to jump out + return } + c.application = providerApplication routerKey := providerApplication + constant.TagRouterRuleSuffix dynamicConfiguration.AddListener(routerKey, c) - //get rule + // get rule rule, err := dynamicConfiguration.GetRule(routerKey, config_center.WithGroup(config_center.DEFAULT_GROUP)) if len(rule) == 0 || err != nil { logger.Errorf("Get rule fail, config rule{%s}, error{%v}", rule, err) @@ -174,71 +268,52 @@ func (c *tagRouter) Notify(invokers []protocol.Invoker) { } } -// URL gets the url of tagRouter -func (c *tagRouter) URL() common.URL { - return *c.url +// ShouldPool returns false, to make sure address cache for tag router happens once and only once. +func (c *tagRouter) ShouldPool() bool { + c.mutex.RLock() + defer c.mutex.RUnlock() + return c.ruleChanged } -// Priority gets the priority of tagRouter -func (c *tagRouter) Priority() int64 { - return c.priority +// Name returns pool's name +func (c *tagRouter) Name() string { + return name } -// filterUsingStaticTag gets a list of invoker using static tag -func filterUsingStaticTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation) []protocol.Invoker { - if tag, ok := invocation.Attachments()[constant.Tagkey]; ok { - result := make([]protocol.Invoker, 0, 8) - 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 +// poolWithDynamicTag pools addresses with the tags defined in dynamic tag rule, all keys have prefix "dynamic-" +func poolWithDynamicTag(invokers []protocol.Invoker, rule *RouterRule, pool router.AddrPool) { + if rule == nil { + return } - return invokers -} -// filterInvokersWithTag gets a list of invoker using dynamic route with tag -func filterInvokersWithTag(invokers []protocol.Invoker, url *common.URL, invocation protocol.Invocation, tagRouterRule RouterRule, tag string) []protocol.Invoker { - var ( - result []protocol.Invoker - addresses []string - ) - addresses, _ = tagRouterRule.getTagNameToAddresses()[tag] - // filter by dynamic tag group first - if len(addresses) > 0 { - filterAddressMatches := func(invoker protocol.Invoker) bool { - url := invoker.GetUrl() - return len(addresses) > 0 && checkAddressMatch(addresses, url.Ip, url.Port) - } - result = filterInvoker(invokers, filterAddressMatches) - if len(result) > 0 || tagRouterRule.Force { - return result - } + tagNameToAddresses := rule.getTagNameToAddresses() + for tag, addrs := range tagNameToAddresses { + pool[dynamicPrefix+tag] = addrsToBitmap(addrs, invokers) } - // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by - // dynamic tag group but force=false. check static tag - filter := func(invoker protocol.Invoker) bool { - return invoker.GetUrl().GetParam(constant.Tagkey, "") == tag - } - result = filterInvoker(invokers, filter) - // If there's no tagged providers that can match the current tagged request. force.tag is set by default - // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed. - if len(result) > 0 || isForceUseTag(url, invocation) { - return result - } - // FAILOVER: return all Providers without any tags. - filterAddressNotMatches := func(invoker protocol.Invoker) bool { +} + +// poolWithStaticTag pools addresses with tags found from incoming URLs, all keys have prefix "static-" +func poolWithStaticTag(invokers []protocol.Invoker, pool router.AddrPool) { + for i, invoker := range invokers { url := invoker.GetUrl() - return len(addresses) == 0 || !checkAddressMatch(tagRouterRule.getAddresses(), url.Ip, url.Port) - } - filterTagIsEmpty := func(invoker protocol.Invoker) bool { - return invoker.GetUrl().GetParam(constant.Tagkey, "") == "" + tag := url.GetParam(constant.Tagkey, "") + if len(tag) > 0 { + if _, ok := pool[staticPrefix+tag]; !ok { + pool[staticPrefix+tag] = roaring.NewBitmap() + } + pool[staticPrefix+tag].AddInt(i) + } } - return filterInvoker(invokers, filterAddressNotMatches, filterTagIsEmpty) +} + +// shouldUseDynamicTag uses the snapshot data from the parsed rule to decide if dynamic tag rule should be used or not +func shouldUseDynamicTag(meta router.AddrMetadata) bool { + return meta.(*addrMetadata).ruleValid && meta.(*addrMetadata).ruleEnabled +} + +// isTagRuleForce uses the snapshot data from the parsed rule to decide if dynamic tag rule is forced or not +func isTagRuleForce(meta router.AddrMetadata) bool { + return meta.(*addrMetadata).ruleForce } // isForceUseTag returns whether force use tag @@ -249,31 +324,46 @@ func isForceUseTag(url *common.URL, invocation protocol.Invocation) bool { return false } -type filter func(protocol.Invoker) bool +// addrsToBitmap finds indexes for the given IP addresses in the target URL list, if any '0.0.0.0' IP address is met, +// then returns back all indexes of the URLs list. +func addrsToBitmap(addrs []string, invokers []protocol.Invoker) *roaring.Bitmap { + ret := roaring.NewBitmap() + for _, addr := range addrs { + if isAnyHost(addr) { + ret.AddRange(0, uint64(len(invokers))) + return ret + } -func filterInvoker(invokers []protocol.Invoker, filters ...filter) []protocol.Invoker { - var res []protocol.Invoker -OUTER: - for _, invoker := range invokers { - for _, filter := range filters { - if !filter(invoker) { - continue OUTER - } + index := findIndexWithIp(addr, invokers) + if index != -1 { + ret.AddInt(index) } - res = append(res, invoker) } - return res + return ret } -func checkAddressMatch(addresses []string, host, port string) bool { - addr := net.JoinHostPort(constant.ANYHOST_VALUE, port) - for _, address := range addresses { - if gxnet.MatchIP(address, host, port) { - return true - } - if address == addr { - return true +// findIndexWithIp finds index for one particular IP +func findIndexWithIp(addr string, invokers []protocol.Invoker) int { + for i, invoker := range invokers { + if gxnet.MatchIP(addr, invoker.GetUrl().Ip, invoker.GetUrl().Port) { + return i } } - return false + return -1 +} + +// isAnyHost checks if an IP is '0.0.0.0' +func isAnyHost(addr string) bool { + return strings.HasPrefix(addr, constant.ANYHOST_VALUE) +} + +// findTag finds tag, first from invocation's attachment, then from URL +func findTag(invocation protocol.Invocation, consumerUrl *common.URL) string { + tag, ok := invocation.Attachments()[constant.Tagkey] + if !ok { + return consumerUrl.GetParam(constant.Tagkey, "") + } else if v, t := tag.(string); t { + return v + } + return "" } diff --git a/cluster/router/tag/tag_router_test.go b/cluster/router/tag/tag_router_test.go index 6b9a5e08f133403565d8b2dd38e03a789c5e95e7..3f7b979f3d52d51ab50c94ea32e87b5eb4f9591e 100644 --- a/cluster/router/tag/tag_router_test.go +++ b/cluster/router/tag/tag_router_test.go @@ -25,15 +25,18 @@ import ( ) import ( + "github.com/RoaringBitmap/roaring" "github.com/dubbogo/go-zookeeper/zk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) import ( + "github.com/apache/dubbo-go/cluster/router" + "github.com/apache/dubbo-go/cluster/router/chain" + "github.com/apache/dubbo-go/cluster/router/utils" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/config" - "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/config_center" _ "github.com/apache/dubbo-go/config_center/zookeeper" @@ -76,13 +79,13 @@ var ( // MockInvoker is only mock the Invoker to support test tagRouter type MockInvoker struct { - url common.URL + url *common.URL available bool destroyed bool successCount int } -func NewMockInvoker(url common.URL) *MockInvoker { +func NewMockInvoker(url *common.URL) *MockInvoker { return &MockInvoker{ url: url, available: true, @@ -91,7 +94,7 @@ func NewMockInvoker(url common.URL) *MockInvoker { } } -func (bi *MockInvoker) GetUrl() common.URL { +func (bi *MockInvoker) GetUrl() *common.URL { return bi.url } @@ -118,7 +121,7 @@ func (bi *MockInvoker) Destroy() { func TestTagRouterPriority(t *testing.T) { u1, err := common.NewURL(tagRouterTestUserConsumerTag) assert.Nil(t, err) - tagRouter, e := NewTagRouter(&u1) + tagRouter, e := NewTagRouter(u1) assert.Nil(t, e) p := tagRouter.Priority() assert.Equal(t, int64(0), p) @@ -127,7 +130,7 @@ func TestTagRouterPriority(t *testing.T) { func TestTagRouterRouteForce(t *testing.T) { u1, e1 := common.NewURL(tagRouterTestUserConsumerTag) assert.Nil(t, e1) - tagRouter, e := NewTagRouter(&u1) + tagRouter, e := NewTagRouter(u1) assert.Nil(t, e) u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) @@ -143,23 +146,23 @@ func TestTagRouterRouteForce(t *testing.T) { invokers = append(invokers, inv2, inv3, inv4) inv := &invocation.RPCInvocation{} inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou) - invRst1 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 1, len(invRst1)) - assert.Equal(t, tagRouterTestHangZhou, invRst1[0].GetUrl().GetParam(tagRouterTestDubboTag, "")) + invRst1 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), u1, inv) + assert.Equal(t, 1, len(invRst1.ToArray())) + assert.Equal(t, tagRouterTestHangZhou, invokers[invRst1.ToArray()[0]].GetUrl().GetParam(tagRouterTestDubboTag, "")) inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) - invRst2 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 0, len(invRst2)) + invRst2 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), u1, inv) + assert.Equal(t, 0, len(invRst2.ToArray())) inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse) inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) - invRst3 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 3, len(invRst3)) + invRst3 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), u1, inv) + assert.Equal(t, 3, len(invRst3.ToArray())) } func TestTagRouterRouteNoForce(t *testing.T) { u1, e1 := common.NewURL(tagRouterTestUserConsumer) assert.Nil(t, e1) - tagRouter, e := NewTagRouter(&u1) + tagRouter, e := NewTagRouter(u1) assert.Nil(t, e) u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) @@ -175,20 +178,37 @@ func TestTagRouterRouteNoForce(t *testing.T) { invokers = append(invokers, inv2, inv3, inv4) inv := &invocation.RPCInvocation{} inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestHangZhou) - invRst := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 1, len(invRst)) - assert.Equal(t, tagRouterTestHangZhou, invRst[0].GetUrl().GetParam(tagRouterTestDubboTag, "")) + invRst := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), u1, inv) + assert.Equal(t, 1, len(invRst.ToArray())) + assert.Equal(t, tagRouterTestHangZhou, invokers[invRst.ToArray()[0]].GetUrl().GetParam(tagRouterTestDubboTag, "")) inv.SetAttachments(tagRouterTestDubboTag, tagRouterTestGuangZhou) inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestTrue) - invRst1 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 0, len(invRst1)) + invRst1 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), u1, inv) + assert.Equal(t, 0, len(invRst1.ToArray())) inv.SetAttachments(tagRouterTestDubboForceTag, tagRouterTestFalse) - invRst2 := tagRouter.Route(invokers, &u1, inv) - assert.Equal(t, 3, len(invRst2)) + invRst2 := tagRouter.Route(utils.ToBitmap(invokers), setUpAddrCache(tagRouter, invokers), u1, inv) + assert.Equal(t, 3, len(invRst2.ToArray())) } -func TestFilterInvoker(t *testing.T) { +func setUpAddrCache(r router.Poolable, addrs []protocol.Invoker) router.Cache { + pool, info := r.Pool(addrs) + cache := chain.BuildCache(addrs) + cache.SetAddrPool(r.Name(), pool) + cache.SetAddrMeta(r.Name(), info) + return cache +} + +func setUpAddrCacheWithRuleDisabled(r router.Poolable, addrs []protocol.Invoker) router.Cache { + pool, info := r.Pool(addrs) + info.(*addrMetadata).ruleEnabled = false + cache := chain.BuildCache(addrs) + cache.SetAddrPool(r.Name(), pool) + cache.SetAddrMeta(r.Name(), info) + return cache +} + +func TestRouteBeijingInvoker(t *testing.T) { u2, e2 := common.NewURL(tagRouterTestHangZhouUrl) u3, e3 := common.NewURL(tagRouterTestShangHaiUrl) u4, e4 := common.NewURL(tagRouterTestBeijingUrl) @@ -203,23 +223,17 @@ func TestFilterInvoker(t *testing.T) { inv5 := NewMockInvoker(u5) var invokers []protocol.Invoker invokers = append(invokers, inv2, inv3, inv4, inv5) - filterTag := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParam(constant.Tagkey, "") == "beijing" { - return true - } - return false - } - res := filterInvoker(invokers, filterTag) - assert.Equal(t, []protocol.Invoker{inv4, inv5}, res) - flag := true - filterEnabled := func(invoker protocol.Invoker) bool { - if invoker.GetUrl().GetParamBool(constant.RouterEnabled, false) == flag { - return true - } - return false - } - res2 := filterInvoker(invokers, filterTag, filterEnabled) - assert.Equal(t, []protocol.Invoker{inv4}, res2) + + url, _ := common.NewURL(tagRouterTestBeijingUrl) + tagRouter, _ := NewTagRouter(url) + + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCache(tagRouter, invokers) + inv := &invocation.RPCInvocation{} + res := tagRouter.Route(rb, cache, url, inv) + // inv4 and inv5 + assert.Equal(t, []uint32{2, 3}, res.ToArray()) } type DynamicTagRouter struct { @@ -278,7 +292,7 @@ tags: suite.NoError(err) zkUrl, _ := common.NewURL(fmt.Sprintf(zkFormat, routerLocalIP, suite.testCluster.Servers[0].Port)) - configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(&zkUrl) + configuration, err := extension.GetConfigCenterFactory(routerZk).GetDynamicConfiguration(zkUrl) config.GetEnvInstance().SetDynamicConfiguration(configuration) suite.Nil(err) @@ -287,11 +301,11 @@ tags: url, e1 := common.NewURL(tagRouterTestUserConsumerTag) suite.Nil(e1) - tagRouter, err := NewTagRouter(&url) + tagRouter, err := NewTagRouter(url) suite.Nil(err) suite.NotNil(tagRouter) suite.route = tagRouter - suite.url = &url + suite.url = url } func (suite *DynamicTagRouter) TearDownTest() { @@ -301,50 +315,57 @@ func (suite *DynamicTagRouter) TearDownTest() { func (suite *DynamicTagRouter) TestDynamicTagRouterSetByIPv4() { invokers := suite.invokers - suite.route.Notify(invokers) + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCache(suite.route, invokers) suite.NotNil(suite.route.tagRouterRule) consumer := &invocation.RPCInvocation{} consumer.SetAttachments(tagRouterTestDubboTag, "tag1") - targetInvokers := suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[0]) + targetInvokers := suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(0)) consumer.SetAttachments(tagRouterTestDubboTag, "tag3") - targetInvokers = suite.route.Route(invokers, suite.url, consumer) - suite.Equal(2, len(targetInvokers)) - suite.Equal(targetInvokers, []protocol.Invoker{suite.invokers[2], suite.invokers[3]}) + targetInvokers = suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(2), targetInvokers.GetCardinality()) + suite.True(targetInvokers.Contains(2) && targetInvokers.Contains(3)) } func (suite *DynamicTagRouter) TestDynamicTagRouterStaticTag() { invokers := suite.invokers + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCacheWithRuleDisabled(suite.route, invokers) consumer := &invocation.RPCInvocation{} consumer.SetAttachments(tagRouterTestDubboTag, "tag4") - targetInvokers := suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[3]) + targetInvokers := suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(3)) } // Teas no tag and return a address are not in dynamic tag group func (suite *DynamicTagRouter) TestDynamicTagRouterByNoTagAndAddressMatch() { invokers := suite.invokers - suite.route.Notify(invokers) + rb := roaring.NewBitmap() + rb.AddRange(0, uint64(len(invokers))) + cache := setUpAddrCache(suite.route, invokers) suite.NotNil(suite.route.tagRouterRule) consumer := &invocation.RPCInvocation{} - targetInvokers := suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[4]) + targetInvokers := suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(4)) // test if there are some addresses that are not in any dynamic tag group, continue to filter using the static tag group. consumer.SetAttachments(tagRouterTestDubboTag, "tag5") - targetInvokers = suite.route.Route(invokers, suite.url, consumer) - suite.Equal(1, len(targetInvokers)) - suite.Equal(targetInvokers[0], suite.invokers[4]) + targetInvokers = suite.route.Route(rb, cache, suite.url, consumer) + suite.Equal(uint64(1), targetInvokers.GetCardinality()) + suite.Equal(targetInvokers.ToArray()[0], uint32(4)) } func TestProcess(t *testing.T) { u1, err := common.NewURL(tagRouterTestUserConsumerTag) assert.Nil(t, err) - tagRouter, e := NewTagRouter(&u1) + tagRouter, e := NewTagRouter(u1) assert.Nil(t, e) assert.NotNil(t, tagRouter) diff --git a/cluster/router/utils/bitmap_util.go b/cluster/router/utils/bitmap_util.go new file mode 100644 index 0000000000000000000000000000000000000000..8b4ee5538f339276fb4c41ceefe4df3575723f15 --- /dev/null +++ b/cluster/router/utils/bitmap_util.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 utils + +import ( + "github.com/RoaringBitmap/roaring" +) + +import ( + "github.com/apache/dubbo-go/protocol" +) + +var EmptyAddr = roaring.NewBitmap() + +func JoinIfNotEqual(left *roaring.Bitmap, right *roaring.Bitmap) *roaring.Bitmap { + if !left.Equals(right) { + left = left.Clone() + left.And(right) + } + return left +} + +func FallbackIfJoinToEmpty(left *roaring.Bitmap, right *roaring.Bitmap) *roaring.Bitmap { + ret := JoinIfNotEqual(left, right) + if ret == nil || ret.IsEmpty() { + return right + } + return ret +} + +func ToBitmap(invokers []protocol.Invoker) *roaring.Bitmap { + bitmap := roaring.NewBitmap() + bitmap.AddRange(0, uint64(len(invokers))) + return bitmap +} diff --git a/common/constant/default.go b/common/constant/default.go index 629aa32392a0151046eaaea67287618eae02158d..4165942a615e220f6384a898b07c04bafd39c3b0 100644 --- a/common/constant/default.go +++ b/common/constant/default.go @@ -45,6 +45,7 @@ const ( DEFAULT_REST_CLIENT = "resty" DEFAULT_REST_SERVER = "go-restful" DEFAULT_PORT = 20000 + DEFAULT_SERIALIZATION = HESSIAN2_SERIALIZATION ) const ( diff --git a/common/constant/key.go b/common/constant/key.go index 7c45a1397d8767510f1f8b92f4e82f0ece05c810..c256659acb91ffb2f74473e4f1bc7aafa4b98c38 100644 --- a/common/constant/key.go +++ b/common/constant/key.go @@ -22,31 +22,33 @@ 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" - CATEGORY_KEY = "category" - CHECK_KEY = "check" - ENABLED_KEY = "enabled" - SIDE_KEY = "side" - OVERRIDE_PROVIDERS_KEY = "providerAddresses" - BEAN_NAME_KEY = "bean.name" - GENERIC_KEY = "generic" - CLASSIFIER_KEY = "classifier" - TOKEN_KEY = "token" - LOCAL_ADDR = "local-addr" - REMOTE_ADDR = "remote-addr" - PATH_SEPARATOR = "/" - DUBBO_KEY = "dubbo" - RELEASE_KEY = "release" - ANYHOST_KEY = "anyhost" - SSL_ENABLED_KEY = "ssl-enabled" + GROUP_KEY = "group" + VERSION_KEY = "version" + INTERFACE_KEY = "interface" + MESSAGE_SIZE_KEY = "message_size" + PATH_KEY = "path" + SERVICE_KEY = "service" + METHODS_KEY = "methods" + TIMEOUT_KEY = "timeout" + CATEGORY_KEY = "category" + CHECK_KEY = "check" + ENABLED_KEY = "enabled" + SIDE_KEY = "side" + OVERRIDE_PROVIDERS_KEY = "providerAddresses" + BEAN_NAME_KEY = "bean.name" + GENERIC_KEY = "generic" + CLASSIFIER_KEY = "classifier" + TOKEN_KEY = "token" + LOCAL_ADDR = "local-addr" + REMOTE_ADDR = "remote-addr" + DEFAULT_REMOTING_TIMEOUT = 3000 + RELEASE_KEY = "release" + ANYHOST_KEY = "anyhost" + PORT_KEY = "port" + PROTOCOL_KEY = "protocol" + PATH_SEPARATOR = "/" + DUBBO_KEY = "dubbo" + SSL_ENABLED_KEY = "ssl-enabled" ) const ( @@ -81,6 +83,7 @@ const ( EXECUTE_REJECTED_EXECUTION_HANDLER_KEY = "execute.limit.rejected.handler" PROVIDER_SHUTDOWN_FILTER = "pshutdown" CONSUMER_SHUTDOWN_FILTER = "cshutdown" + SERIALIZATION_KEY = "serialization" PID_KEY = "pid" SYNC_REPORT_KEY = "sync.report" RETRY_PERIOD_KEY = "retry.period" @@ -124,6 +127,7 @@ const ( TAG_ROUTE_PROTOCOL = "tag" PROVIDERS_CATEGORY = "providers" ROUTER_KEY = "router" + EXPORT_KEY = "export" ) const ( @@ -169,6 +173,10 @@ const ( NACOS_USERNAME = "username" ) +const ( + FILE_KEY = "file" +) + const ( ZOOKEEPER_KEY = "zookeeper" ) @@ -177,6 +185,18 @@ const ( ETCDV3_KEY = "etcdv3" ) +const ( + CONSUL_KEY = "consul" + CHECK_PASS_INTERVAL = "consul-check-pass-interval" + // default time-to-live in millisecond + DEFAULT_CHECK_PASS_INTERVAL = 16000 + QUERY_TAG = "consul_query_tag" + ACL_TOKEN = "acl-token" + // default deregister critical server after + DEFAULT_DEREGISTER_TIME = "20s" + DEREGISTER_AFTER = "consul-deregister-critical-service-after" +) + const ( TRACING_REMOTE_SPAN_CTX = "tracing.remote.span.ctx" ) diff --git a/cluster/router/chan.go b/common/constant/serializtion.go similarity index 83% rename from cluster/router/chan.go rename to common/constant/serializtion.go index 6904e1734a7cbdaa00afa1b30797d19ca502453c..f27598ccf5cf04a72d14d4ef97ae9298076efe1a 100644 --- a/cluster/router/chan.go +++ b/common/constant/serializtion.go @@ -15,11 +15,14 @@ * limitations under the License. */ -package router +package constant -// Chain -type Chain interface { - router - // AddRouters Add routers - AddRouters([]PriorityRouter) -} +const ( + S_Hessian2 byte = 2 + S_Proto byte = 21 +) + +const ( + HESSIAN2_SERIALIZATION = "hessian2" + PROTOBUF_SERIALIZATION = "protobuf" +) diff --git a/common/extension/config_post_processor.go b/common/extension/config_post_processor.go new file mode 100644 index 0000000000000000000000000000000000000000..db126b744d54562a7f45b59aec26ef6e950a23a8 --- /dev/null +++ b/common/extension/config_post_processor.go @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package extension + +import ( + "github.com/apache/dubbo-go/config/interfaces" +) + +var ( + processors = make(map[string]interfaces.ConfigPostProcessor) +) + +// SetConfigPostProcessor registers a ConfigPostProcessor with the given name. +func SetConfigPostProcessor(name string, processor interfaces.ConfigPostProcessor) { + processors[name] = processor +} + +// GetConfigPostProcessor finds a ConfigPostProcessor by name. +func GetConfigPostProcessor(name string) interfaces.ConfigPostProcessor { + return processors[name] +} + +// GetConfigPostProcessors returns all registered instances of ConfigPostProcessor. +func GetConfigPostProcessors() []interfaces.ConfigPostProcessor { + ret := make([]interfaces.ConfigPostProcessor, 0, len(processors)) + for _, v := range processors { + ret = append(ret, v) + } + return ret +} diff --git a/common/extension/health_checker.go b/common/extension/health_checker.go index 8def727614dad8393eeef9ced5e30a056fa65461..cec4c2defc291c617a0549c3296e07851b2ec128 100644 --- a/common/extension/health_checker.go +++ b/common/extension/health_checker.go @@ -26,8 +26,8 @@ var ( healthCheckers = make(map[string]func(url *common.URL) router.HealthChecker) ) -// SethealthChecker sets the HealthChecker with @name -func SethealthChecker(name string, fcn func(_ *common.URL) router.HealthChecker) { +// SetHealthChecker sets the HealthChecker with @name +func SetHealthChecker(name string, fcn func(_ *common.URL) router.HealthChecker) { healthCheckers[name] = fcn } diff --git a/common/extension/health_checker_test.go b/common/extension/health_checker_test.go index 4e83a6f6e1ed8a57b6e6374377d08eabfb56c604..af6b114a612a465d4397be7a599ddfc9ff7edab9 100644 --- a/common/extension/health_checker_test.go +++ b/common/extension/health_checker_test.go @@ -32,7 +32,7 @@ import ( ) func TestGetHealthChecker(t *testing.T) { - SethealthChecker("mock", newMockhealthCheck) + SetHealthChecker("mock", newMockHealthCheck) checker := GetHealthChecker("mock", common.NewURLWithOptions()) assert.NotNil(t, checker) } @@ -44,6 +44,6 @@ func (m mockHealthChecker) IsHealthy(invoker protocol.Invoker) bool { return true } -func newMockhealthCheck(_ *common.URL) router.HealthChecker { +func newMockHealthCheck(_ *common.URL) router.HealthChecker { return &mockHealthChecker{} } diff --git a/common/host_util.go b/common/host_util.go new file mode 100644 index 0000000000000000000000000000000000000000..9861c92bee15624dd79e02a6e3aba6b443dbd7da --- /dev/null +++ b/common/host_util.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 common + +import gxnet "github.com/dubbogo/gost/net" + +var localIp string + +func GetLocalIp() string { + if len(localIp) != 0 { + return localIp + } + localIp, _ = gxnet.GetLocalIP() + return localIp +} diff --git a/common/host_util_test.go b/common/host_util_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ad96dde4c77667a940c27455262a6aeca921a73d --- /dev/null +++ b/common/host_util_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 common + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestGetLocalIp(t *testing.T) { + assert.NotNil(t, GetLocalIp()) +} diff --git a/common/node.go b/common/node.go index 4febd78536126c67bdc65fc09d4be47fb869ef5e..b9c1f39bf960b466276edf1e46bb7c1ecddd6f19 100644 --- a/common/node.go +++ b/common/node.go @@ -19,7 +19,7 @@ package common // Node use for process dubbo node type Node interface { - GetUrl() URL + GetUrl() *URL IsAvailable() bool Destroy() } diff --git a/common/proxy/proxy.go b/common/proxy/proxy.go index ce0f4d1d3f4dc8b93467aaeede40ea03b53c6e66..3a01941700ed277d9481c6c57a739c69f83b06b7 100644 --- a/common/proxy/proxy.go +++ b/common/proxy/proxy.go @@ -66,7 +66,6 @@ func NewProxy(invoke protocol.Invoker, callBack interface{}, attachments map[str // 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. valueOf := reflect.ValueOf(v) logger.Debugf("[Implement] reflect.TypeOf: %s", valueOf.String()) @@ -145,12 +144,17 @@ func (p *Proxy) Implement(v common.RPCService) { inv.SetAttachments(k, value) } - // add user setAttachment + // add user setAttachment. It is compatibility with previous versions. atm := invCtx.Value(constant.AttachmentKey) if m, ok := atm.(map[string]string); ok { for k, value := range m { inv.SetAttachments(k, value) } + } else if m2, ok2 := atm.(map[string]interface{}); ok2 { + // it is support to transfer map[string]interface{}. It refers to dubbo-java 2.7. + for k, value := range m2 { + inv.SetAttachments(k, value) + } } result := p.invoke.Invoke(invCtx, inv) @@ -230,3 +234,8 @@ func (p *Proxy) Get() common.RPCService { func (p *Proxy) GetCallback() interface{} { return p.callBack } + +// GetInvoker gets Invoker. +func (p *Proxy) GetInvoker() protocol.Invoker { + return p.invoke +} diff --git a/common/proxy/proxy_factory.go b/common/proxy/proxy_factory.go index 117428cb253e1ad4a4ceee59aa620d7097b41a75..2e66c346b2c8ab6af0b1485f0ea867eb1af0bfc5 100644 --- a/common/proxy/proxy_factory.go +++ b/common/proxy/proxy_factory.go @@ -26,7 +26,7 @@ import ( 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 + GetInvoker(url *common.URL) protocol.Invoker } // Option will define a function of handling ProxyFactory diff --git a/common/proxy/proxy_factory/default.go b/common/proxy/proxy_factory/default.go index 1b8ca222011292040c57c3e86df0438943a5b464..ff3d7955a027411b0697797d32a44023f8f70caf 100644 --- a/common/proxy/proxy_factory/default.go +++ b/common/proxy/proxy_factory/default.go @@ -72,7 +72,7 @@ func (factory *DefaultProxyFactory) GetAsyncProxy(invoker protocol.Invoker, call } // GetInvoker gets a invoker -func (factory *DefaultProxyFactory) GetInvoker(url common.URL) protocol.Invoker { +func (factory *DefaultProxyFactory) GetInvoker(url *common.URL) protocol.Invoker { return &ProxyInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), } @@ -88,7 +88,8 @@ func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocati result := &protocol.RPCResult{} result.SetAttachments(invocation.Attachments()) - url := pi.GetUrl() + //get providerUrl. The origin url may be is registry URL. + url := getProviderURL(pi.GetUrl()) methodName := invocation.MethodName() proto := url.Protocol @@ -96,7 +97,7 @@ func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocati args := invocation.Arguments() // get service - svc := common.ServiceMap.GetService(proto, path) + svc := common.ServiceMap.GetServiceByServiceKey(proto, url.ServiceKey()) if svc == nil { logger.Errorf("cannot find service [%s] in %s", path, proto) result.SetError(perrors.Errorf("cannot find service [%s] in %s", path, proto)) @@ -159,3 +160,10 @@ func (pi *ProxyInvoker) Invoke(ctx context.Context, invocation protocol.Invocati } return result } + +func getProviderURL(url *common.URL) *common.URL { + if url.SubURL == nil { + return url + } + return url.SubURL +} diff --git a/common/proxy/proxy_factory/default_test.go b/common/proxy/proxy_factory/default_test.go index 99d5c020f4bfd0254ddddc65e78bcae2e252b6be..4002ab95849b79aef26c8d94822e731cd6f229e3 100644 --- a/common/proxy/proxy_factory/default_test.go +++ b/common/proxy/proxy_factory/default_test.go @@ -34,7 +34,7 @@ import ( func TestGetProxy(t *testing.T) { proxyFactory := NewDefaultProxyFactory() url := common.NewURLWithOptions() - proxy := proxyFactory.GetProxy(protocol.NewBaseInvoker(*url), url) + proxy := proxyFactory.GetProxy(protocol.NewBaseInvoker(url), url) assert.NotNil(t, proxy) } @@ -49,13 +49,13 @@ func TestGetAsyncProxy(t *testing.T) { proxyFactory := NewDefaultProxyFactory() url := common.NewURLWithOptions() async := &TestAsync{} - proxy := proxyFactory.GetAsyncProxy(protocol.NewBaseInvoker(*url), async.CallBack, url) + proxy := proxyFactory.GetAsyncProxy(protocol.NewBaseInvoker(url), async.CallBack, url) assert.NotNil(t, proxy) } func TestGetInvoker(t *testing.T) { proxyFactory := NewDefaultProxyFactory() url := common.NewURLWithOptions() - invoker := proxyFactory.GetInvoker(*url) + invoker := proxyFactory.GetInvoker(url) assert.True(t, invoker.IsAvailable()) } diff --git a/common/proxy/proxy_test.go b/common/proxy/proxy_test.go index 14b2befbc47242d9cc9a2f88e9070b84828062c0..c6f659666180310887f2101966e59669df707e54 100644 --- a/common/proxy/proxy_test.go +++ b/common/proxy/proxy_test.go @@ -32,6 +32,8 @@ 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/dubbo/hessian2" + "github.com/apache/dubbo-go/protocol/invocation" ) type TestService struct { @@ -40,6 +42,7 @@ type TestService struct { MethodThree func(int, bool) (interface{}, error) MethodFour func(int, bool) (*interface{}, error) `dubbo:"methodFour"` MethodFive func() error + MethodSix func(context.Context, string) (interface{}, error) Echo func(interface{}, *interface{}) error } @@ -55,7 +58,7 @@ func (s *TestServiceInt) Reference() string { func TestProxyImplement(t *testing.T) { - invoker := protocol.NewBaseInvoker(common.URL{}) + invoker := protocol.NewBaseInvoker(&common.URL{}) p := NewProxy(invoker, nil, map[string]string{constant.ASYNC_KEY: "false"}) s := &TestService{} p.Implement(s) @@ -120,3 +123,34 @@ func TestProxyImplement(t *testing.T) { assert.Nil(t, s3.MethodOne) } + +func TestProxyImplementForContext(t *testing.T) { + invoker := &TestProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(&common.URL{}), + } + p := NewProxy(invoker, nil, map[string]string{constant.ASYNC_KEY: "false"}) + s := &TestService{} + p.Implement(s) + attahments1 := make(map[string]interface{}, 4) + attahments1["k1"] = "v1" + attahments1["k2"] = "v2" + context := context.WithValue(context.Background(), constant.AttachmentKey, attahments1) + r, err := p.Get().(*TestService).MethodSix(context, "xxx") + v1 := r.(map[string]interface{}) + assert.NoError(t, err) + assert.Equal(t, v1["TestProxyInvoker"], "TestProxyInvokerValue") +} + +type TestProxyInvoker struct { + protocol.BaseInvoker +} + +func (bi *TestProxyInvoker) Invoke(context context.Context, inv protocol.Invocation) protocol.Result { + rpcInv := inv.(*invocation.RPCInvocation) + mapV := inv.Attachments() + mapV["TestProxyInvoker"] = "TestProxyInvokerValue" + hessian2.ReflectResponse(mapV, rpcInv.Reply()) + return &protocol.RPCResult{ + Rest: inv.Arguments(), + } +} diff --git a/common/rpc_service.go b/common/rpc_service.go index 9ef2b956aa955f4fc79c6f75bd060ccfee2d02ca..572fc71701c411a7a0a070c2a84e9a4d96da6dfb 100644 --- a/common/rpc_service.go +++ b/common/rpc_service.go @@ -157,11 +157,17 @@ type serviceMap struct { } // GetService gets a service defination by protocol and name -func (sm *serviceMap) GetService(protocol, name string) *Service { +func (sm *serviceMap) GetService(protocol, interfaceName, group, version string) *Service { + serviceKey := ServiceKey(interfaceName, group, version) + return sm.GetServiceByServiceKey(protocol, serviceKey) +} + +// GetService gets a service defination by protocol and service key +func (sm *serviceMap) GetServiceByServiceKey(protocol, serviceKey string) *Service { sm.mutex.RLock() defer sm.mutex.RUnlock() if s, ok := sm.serviceMap[protocol]; ok { - if srv, ok := s[name]; ok { + if srv, ok := s[serviceKey]; ok { return srv } return nil @@ -169,7 +175,7 @@ func (sm *serviceMap) GetService(protocol, name string) *Service { return nil } -// GetInterface gets an interface defination by interface name +// GetInterface gets an interface definition by interface name func (sm *serviceMap) GetInterface(interfaceName string) []*Service { sm.mutex.RLock() defer sm.mutex.RUnlock() @@ -180,7 +186,7 @@ func (sm *serviceMap) GetInterface(interfaceName string) []*Service { } // Register registers a service by @interfaceName and @protocol -func (sm *serviceMap) Register(interfaceName, protocol string, rcvr RPCService) (string, error) { +func (sm *serviceMap) Register(interfaceName, protocol, group, version string, rcvr RPCService) (string, error) { if sm.serviceMap[protocol] == nil { sm.serviceMap[protocol] = make(map[string]*Service) } @@ -203,8 +209,8 @@ func (sm *serviceMap) Register(interfaceName, protocol string, rcvr RPCService) return "", perrors.New(s) } - sname = rcvr.Reference() - if server := sm.GetService(protocol, sname); server != nil { + sname = ServiceKey(interfaceName, group, version) + if server := sm.GetService(protocol, interfaceName, group, version); server != nil { return "", perrors.New("service already defined: " + sname) } s.name = sname @@ -228,9 +234,9 @@ func (sm *serviceMap) Register(interfaceName, protocol string, rcvr RPCService) } // 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") +func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceKey string) error { + if protocol == "" || serviceKey == "" { + return perrors.New("protocol or serviceKey is nil") } var ( @@ -248,9 +254,9 @@ func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) erro if !ok { return perrors.New("no services for " + protocol) } - s, ok := svcs[serviceId] + s, ok := svcs[serviceKey] if !ok { - return perrors.New("no service for " + serviceId) + return perrors.New("no service for " + serviceKey) } svrs, ok = sm.interfaceMap[interfaceName] if !ok { @@ -271,12 +277,12 @@ func (sm *serviceMap) UnRegister(interfaceName, protocol, serviceId string) erro sm.mutex.Lock() defer sm.mutex.Unlock() sm.interfaceMap[interfaceName] = make([]*Service, 0, len(svrs)) - for i, _ := range svrs { + for i := range svrs { if i != index { sm.interfaceMap[interfaceName] = append(sm.interfaceMap[interfaceName], svrs[i]) } } - delete(svcs, serviceId) + delete(svcs, serviceKey) if len(sm.serviceMap[protocol]) == 0 { delete(sm.serviceMap, protocol) } diff --git a/common/rpc_service_test.go b/common/rpc_service_test.go index 7c4eb421d7ed0e5e0306eb1a1a2ff5ac31c29b6d..b50d8d962c4277f702388b74e5137bdf1c8bdf7e 100644 --- a/common/rpc_service_test.go +++ b/common/rpc_service_test.go @@ -85,23 +85,23 @@ func TestServiceMapRegister(t *testing.T) { // lowercase s0 := &testService{} // methods, err := ServiceMap.Register("testporotocol", s0) - _, err := ServiceMap.Register(testInterfaceName, "testporotocol", s0) + _, err := ServiceMap.Register(testInterfaceName, "testporotocol", "", "v0", s0) assert.EqualError(t, err, "type testService is not exported") // succ s := &TestService{} - methods, err := ServiceMap.Register(testInterfaceName, "testporotocol", s) + methods, err := ServiceMap.Register(testInterfaceName, "testporotocol", "", "v1", s) assert.NoError(t, err) assert.Equal(t, "MethodOne,MethodThree,methodTwo", methods) // repeat - _, err = ServiceMap.Register(testInterfaceName, "testporotocol", s) - assert.EqualError(t, err, "service already defined: com.test.Path") + _, err = ServiceMap.Register(testInterfaceName, "testporotocol", "", "v1", s) + assert.EqualError(t, err, "service already defined: testService:v1") // no method s1 := &TestService1{} - _, err = ServiceMap.Register(testInterfaceName, "testporotocol", s1) - assert.EqualError(t, err, "type com.test.Path1 has no exported methods of suitable type") + _, err = ServiceMap.Register(testInterfaceName, "testporotocol", "", "v2", s1) + assert.EqualError(t, err, "type testService:v2 has no exported methods of suitable type") ServiceMap = &serviceMap{ serviceMap: make(map[string]map[string]*Service), @@ -111,22 +111,22 @@ func TestServiceMapRegister(t *testing.T) { func TestServiceMapUnRegister(t *testing.T) { s := &TestService{} - _, err := ServiceMap.Register("TestService", testProtocol, s) + _, err := ServiceMap.Register("TestService", testProtocol, "", "v1", s) assert.NoError(t, err) - assert.NotNil(t, ServiceMap.GetService(testProtocol, referenceTestPath)) + assert.NotNil(t, ServiceMap.GetService(testProtocol, "TestService", "", "v1")) assert.Equal(t, 1, len(ServiceMap.GetInterface("TestService"))) - err = ServiceMap.UnRegister("", "", referenceTestPath) - assert.EqualError(t, err, "protocol or serviceName is nil") + err = ServiceMap.UnRegister("", "", ServiceKey("TestService", "", "v1")) + assert.EqualError(t, err, "protocol or serviceKey is nil") - err = ServiceMap.UnRegister("", "protocol", referenceTestPath) + err = ServiceMap.UnRegister("", "protocol", ServiceKey("TestService", "", "v1")) assert.EqualError(t, err, "no services for protocol") - err = ServiceMap.UnRegister("", testProtocol, referenceTestPathDistinct) - assert.EqualError(t, err, "no service for com.test.Path1") + err = ServiceMap.UnRegister("", testProtocol, ServiceKey("TestService", "", "v0")) + assert.EqualError(t, err, "no service for TestService:v0") // succ - err = ServiceMap.UnRegister("TestService", testProtocol, referenceTestPath) + err = ServiceMap.UnRegister("TestService", testProtocol, ServiceKey("TestService", "", "v1")) assert.NoError(t, err) } diff --git a/common/url.go b/common/url.go index ec6dce9175596e4f1774614f8f0cb978d181f300..1ea2bc4321afcf93f148fea07019a892966fd720 100644 --- a/common/url.go +++ b/common/url.go @@ -18,13 +18,16 @@ package common import ( + "bytes" "encoding/base64" "fmt" + cm "github.com/Workiva/go-datastructures/common" "math" "net" "net/url" "strconv" "strings" + "sync" ) import ( @@ -61,8 +64,14 @@ var ( DubboNodes = [...]string{"consumers", "configurators", "routers", "providers"} // DubboRole Dubbo service role DubboRole = [...]string{"consumer", "", "routers", "provider"} + // CompareURLEqualFunc compare two url is equal + compareURLEqualFunc CompareURLEqualFunc ) +func init() { + compareURLEqualFunc = defaultCompareURLEqual +} + // nolint type RoleType int @@ -76,22 +85,37 @@ func (t RoleType) Role() string { } type baseUrl struct { - Protocol string - Location string // ip+port - Ip string - Port string + 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 params url.Values PrimitiveURL string } -// URL is not thread-safe. +// noCopy may be embedded into structs which must not be copied +// after the first use. +// +// See https://golang.org/issues/8005#issuecomment-190753527 +// for details. +type noCopy struct{} + +// Lock is a no-op used by -copylocks checker from `go vet`. +func (*noCopy) Lock() {} +func (*noCopy) Unlock() {} + +// URL thread-safe. but this url should not be copied. // 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 { + noCopy noCopy + baseUrl - Path string // like /com.ikurento.dubbo.UserProvider3 + Path string // like /com.ikurento.dubbo.UserProvider Username string Password string Methods []string @@ -101,80 +125,80 @@ type URL struct { // Option accepts url // Option will define a function of handling URL -type option func(*URL) +type Option func(*URL) // WithUsername sets username for url -func WithUsername(username string) option { +func WithUsername(username string) Option { return func(url *URL) { url.Username = username } } // WithPassword sets password for url -func WithPassword(pwd string) option { +func WithPassword(pwd string) Option { return func(url *URL) { url.Password = pwd } } // WithMethods sets methods for url -func WithMethods(methods []string) option { +func WithMethods(methods []string) Option { return func(url *URL) { url.Methods = methods } } // WithParams sets params for url -func WithParams(params url.Values) option { +func WithParams(params url.Values) Option { return func(url *URL) { url.params = params } } // WithParamsValue sets params field for url -func WithParamsValue(key, val string) option { +func WithParamsValue(key, val string) Option { return func(url *URL) { url.SetParam(key, val) } } // WithProtocol sets protocol for url -func WithProtocol(proto string) option { +func WithProtocol(proto string) Option { return func(url *URL) { url.Protocol = proto } } // WithIp sets ip for url -func WithIp(ip string) option { +func WithIp(ip string) Option { return func(url *URL) { url.Ip = ip } } // WithPort sets port for url -func WithPort(port string) option { +func WithPort(port string) Option { return func(url *URL) { url.Port = port } } // WithPath sets path for url -func WithPath(path string) option { +func WithPath(path string) Option { return func(url *URL) { url.Path = "/" + strings.TrimPrefix(path, "/") } } // WithLocation sets location for url -func WithLocation(location string) option { +func WithLocation(location string) Option { return func(url *URL) { url.Location = location } } // WithToken sets token for url -func WithToken(token string) option { +func WithToken(token string) Option { return func(url *URL) { if len(token) > 0 { value := token @@ -192,51 +216,45 @@ func WithToken(token string) option { } // NewURLWithOptions will create a new url with options -func NewURLWithOptions(opts ...option) *URL { - url := &URL{} +func NewURLWithOptions(opts ...Option) *URL { + newUrl := &URL{} for _, opt := range opts { - opt(url) + opt(newUrl) } - url.Location = url.Ip + ":" + url.Port - return url + newUrl.Location = newUrl.Ip + ":" + newUrl.Port + return newUrl } // NewURL will create a new url // the urlString should not be empty -func NewURL(urlString string, opts ...option) (URL, error) { - var ( - err error - rawUrlString string - serviceUrl *url.URL - s = URL{baseUrl: baseUrl{}} - ) - - // new a null instance +func NewURL(urlString string, opts ...Option) (*URL, error) { + s := URL{baseUrl: baseUrl{}} if urlString == "" { - return s, nil + return &s, nil } - rawUrlString, err = url.QueryUnescape(urlString) + rawUrlString, err := url.QueryUnescape(urlString) if err != nil { - return s, perrors.Errorf("url.QueryUnescape(%s), error{%v}", urlString, err) + return &s, perrors.Errorf("url.QueryUnescape(%s), error{%v}", urlString, err) } // rawUrlString = "//" + rawUrlString - if strings.Index(rawUrlString, "//") < 0 { + if !strings.Contains(rawUrlString, "//") { t := URL{baseUrl: baseUrl{}} for _, opt := range opts { opt(&t) } rawUrlString = t.Protocol + "://" + rawUrlString } - serviceUrl, err = url.Parse(rawUrlString) - if err != nil { - return s, perrors.Errorf("url.Parse(url string{%s}), error{%v}", rawUrlString, err) + + serviceUrl, urlParseErr := url.Parse(rawUrlString) + if urlParseErr != nil { + return &s, perrors.Errorf("url.Parse(url string{%s}), error{%v}", rawUrlString, err) } s.params, err = url.ParseQuery(serviceUrl.RawQuery) if err != nil { - return s, perrors.Errorf("url.ParseQuery(raw url string{%s}), error{%v}", serviceUrl.RawQuery, err) + return &s, perrors.Errorf("url.ParseQuery(raw url string{%s}), error{%v}", serviceUrl.RawQuery, err) } s.PrimitiveURL = urlString @@ -248,25 +266,29 @@ func NewURL(urlString string, opts ...option) (URL, error) { if strings.Contains(s.Location, ":") { s.Ip, s.Port, err = net.SplitHostPort(s.Location) if err != nil { - return s, perrors.Errorf("net.SplitHostPort(url.Host{%s}), error{%v}", s.Location, err) + return &s, perrors.Errorf("net.SplitHostPort(url.Host{%s}), error{%v}", s.Location, err) } } for _, opt := range opts { opt(&s) } - return s, nil + return &s, nil } // URLEqual judge @url and @c is equal or not. -func (c URL) URLEqual(url URL) bool { - c.Ip = "" - c.Port = "" - url.Ip = "" - url.Port = "" - cGroup := c.GetParam(constant.GROUP_KEY, "") - urlGroup := url.GetParam(constant.GROUP_KEY, "") - cKey := c.Key() - urlKey := url.Key() +func (c *URL) URLEqual(url *URL) bool { + tmpC := c.Clone() + tmpC.Ip = "" + tmpC.Port = "" + + tmpUrl := url.Clone() + tmpUrl.Ip = "" + tmpUrl.Port = "" + + cGroup := tmpC.GetParam(constant.GROUP_KEY, "") + urlGroup := tmpUrl.GetParam(constant.GROUP_KEY, "") + cKey := tmpC.Key() + urlKey := tmpUrl.Key() if cGroup == constant.ANY_VALUE { cKey = strings.Replace(cKey, "group=*", "group="+urlGroup, 1) @@ -280,12 +302,12 @@ func (c URL) URLEqual(url URL) bool { } // 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 { + if tmpUrl.GetParam(constant.ENABLED_KEY, "true") != "true" && tmpUrl.GetParam(constant.ENABLED_KEY, "") != constant.ANY_VALUE { return false } // 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)) + return isMatchCategory(tmpUrl.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY), tmpC.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY)) } func isMatchCategory(category1 string, category2 string) bool { @@ -300,37 +322,37 @@ func isMatchCategory(category1 string, category2 string) bool { } } -func (c URL) String() string { +func (c *URL) String() string { + c.paramsLock.Lock() + defer c.paramsLock.Unlock() var buf strings.Builder if len(c.Username) == 0 && len(c.Password) == 0 { - buf.WriteString(fmt.Sprintf( - "%s://%s:%s%s?", - c.Protocol, c.Ip, c.Port, c.Path)) + buf.WriteString(fmt.Sprintf("%s://%s:%s%s?", c.Protocol, c.Ip, c.Port, c.Path)) } else { - buf.WriteString(fmt.Sprintf( - "%s://%s:%s@%s:%s%s?", - c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path)) + buf.WriteString(fmt.Sprintf("%s://%s:%s@%s:%s%s?", c.Protocol, c.Username, c.Password, c.Ip, c.Port, c.Path)) } buf.WriteString(c.params.Encode()) return buf.String() } // Key gets key -func (c URL) Key() string { - buildString := fmt.Sprintf( - "%s://%s:%s@%s:%s/?interface=%s&group=%s&version=%s", +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 } // ServiceKey gets a unique key of a service. -func (c URL) ServiceKey() string { - intf := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) +func (c *URL) ServiceKey() string { + return ServiceKey(c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")), + c.GetParam(constant.GROUP_KEY, ""), c.GetParam(constant.VERSION_KEY, "")) +} + +func ServiceKey(intf string, group string, version string) string { if intf == "" { return "" } - var buf strings.Builder - group := c.GetParam(constant.GROUP_KEY, "") + buf := &bytes.Buffer{} if group != "" { buf.WriteString(group) buf.WriteString("/") @@ -338,7 +360,6 @@ func (c URL) ServiceKey() string { buf.WriteString(intf) - version := c.GetParam(constant.VERSION_KEY, "") if version != "" && version != "0.0.0" { buf.WriteString(":") buf.WriteString(version) @@ -376,7 +397,7 @@ func (c *URL) EncodedServiceKey() string { } // Service gets service -func (c URL) Service() string { +func (c *URL) Service() string { service := c.GetParam(constant.INTERFACE_KEY, strings.TrimPrefix(c.Path, "/")) if service != "" { return service @@ -390,23 +411,49 @@ func (c URL) Service() string { } // 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() + defer c.paramsLock.Unlock() + c.params.Add(key, value) +} + +// AddParamAvoidNil will add key-value pair +func (c *URL) AddParamAvoidNil(key string, value string) { + c.paramsLock.Lock() + defer c.paramsLock.Unlock() + if c.params == nil { + c.params = url.Values{} + } c.params.Add(key, value) } // 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() + defer c.paramsLock.Unlock() c.params.Set(key, value) } +// DelParam will delete the given key from the url +func (c *URL) DelParam(key string) { + c.paramsLock.Lock() + defer c.paramsLock.Unlock() + c.params.Del(key) +} + +// ReplaceParams will replace the URL.params +// usually it should only be invoked when you want to modify an url, such as MergeURL +func (c *URL) ReplaceParams(param url.Values) { + c.paramsLock.Lock() + defer c.paramsLock.Unlock() + c.params = param +} + // 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 @@ -415,7 +462,9 @@ func (c *URL) RangeParams(f func(key, value string) bool) { } // GetParam gets value by key -func (c URL) GetParam(s string, d string) string { +func (c *URL) GetParam(s string, d string) string { + c.paramsLock.RLock() + defer c.paramsLock.RUnlock() r := c.params.Get(s) if len(r) == 0 { r = d @@ -424,19 +473,19 @@ func (c URL) GetParam(s string, d string) string { } // GetParams gets values -func (c URL) GetParams() url.Values { +func (c *URL) GetParams() url.Values { return c.params } // GetParamAndDecoded gets values and decode -func (c URL) GetParamAndDecoded(key string) (string, error) { +func (c *URL) GetParamAndDecoded(key string) (string, error) { ruleDec, err := base64.URLEncoding.DecodeString(c.GetParam(key, "")) value := string(ruleDec) return value, err } // GetRawParam gets raw param -func (c URL) GetRawParam(key string) string { +func (c *URL) GetRawParam(key string) string { switch key { case PROTOCOL: return c.Protocol @@ -456,7 +505,7 @@ func (c URL) GetRawParam(key string) string { } // GetParamBool judge whether @key exists or not -func (c URL) GetParamBool(key string, d bool) bool { +func (c *URL) GetParamBool(key string, d bool) bool { r, err := strconv.ParseBool(c.GetParam(key, "")) if err != nil { return d @@ -464,26 +513,53 @@ func (c URL) GetParamBool(key string, d bool) bool { return r } -// 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 { +// GetParamInt gets int64 value by @key +func (c *URL) GetParamInt(key string, d int64) int64 { + r, err := strconv.ParseInt(c.GetParam(key, ""), 10, 64) + if err != nil { + return d + } + return r +} + +// GetParamInt32 gets int32 value by @key +func (c *URL) GetParamInt32(key string, d int32) int32 { + r, err := strconv.ParseInt(c.GetParam(key, ""), 10, 32) + if err != nil { + return d + } + return int32(r) +} + +// GetParamByIntValue gets int value by @key +func (c *URL) GetParamByIntValue(key string, d int) int { + r, err := strconv.ParseInt(c.GetParam(key, ""), 10, 0) + if err != nil { return d } - return int64(r) + return int(r) } // GetMethodParamInt gets int method param -func (c URL) GetMethodParamInt(method string, key string, d int64) int64 { - r, err := strconv.Atoi(c.GetParam("methods."+method+"."+key, "")) - if r == 0 || err != nil { +func (c *URL) GetMethodParamInt(method string, key string, d int64) int64 { + r, err := strconv.ParseInt(c.GetParam("methods."+method+"."+key, ""), 10, 64) + if err != nil { return d } - return int64(r) + return r +} + +// GetMethodParamIntValue gets int method param +func (c *URL) GetMethodParamIntValue(method string, key string, d int) int { + r, err := strconv.ParseInt(c.GetParam("methods."+method+"."+key, ""), 10, 0) + if err != nil { + return d + } + return int(r) } // GetMethodParamInt64 gets int64 method param -func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 { +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) @@ -492,7 +568,7 @@ func (c URL) GetMethodParamInt64(method string, key string, d int64) int64 { } // GetMethodParam gets method param -func (c URL) GetMethodParam(method string, key string, d string) string { +func (c *URL) GetMethodParam(method string, key string, d string) string { r := c.GetParam("methods."+method+"."+key, "") if r == "" { r = d @@ -501,15 +577,15 @@ func (c URL) GetMethodParam(method string, key string, d string) string { } // GetMethodParamBool judge whether @method param exists or not -func (c URL) GetMethodParamBool(method string, key string, d bool) bool { +func (c *URL) GetMethodParamBool(method string, key string, d bool) bool { r := c.GetParamBool("methods."+method+"."+key, d) return r } -// 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 +//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)) @@ -517,7 +593,7 @@ func (c *URL) SetParams(m url.Values) { } // ToMap transfer URL to Map -func (c URL) ToMap() map[string]string { +func (c *URL) ToMap() map[string]string { paramsMap := make(map[string]string) c.RangeParams(func(key, value string) bool { @@ -568,22 +644,26 @@ func (c URL) ToMap() map[string]string { // 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 { + // After Clone, it is a new url that there is no thread safe issue. mergedUrl := serviceUrl.Clone() - + params := mergedUrl.GetParams() // iterator the referenceUrl if serviceUrl not have the key ,merge in - referenceUrl.RangeParams(func(key, value string) bool { + // referenceUrl usually will not changed. so change RangeParams to GetParams to avoid the string value copy. + for key, value := range referenceUrl.GetParams() { if v := mergedUrl.GetParam(key, ""); len(v) == 0 { - mergedUrl.SetParam(key, value) + if len(value) > 0 { + params[key] = value + } } - return true - }) + } + // loadBalance,cluster,retries strategy config - methodConfigMergeFcn := mergeNormalParam(mergedUrl, referenceUrl, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY, constant.TIMEOUT_KEY}) + methodConfigMergeFcn := mergeNormalParam(params, referenceUrl, []string{constant.LOADBALANCE_KEY, constant.CLUSTER_KEY, constant.RETRIES_KEY, constant.TIMEOUT_KEY}) // remote timestamp if v := serviceUrl.GetParam(constant.TIMESTAMP_KEY, ""); len(v) > 0 { - mergedUrl.SetParam(constant.REMOTE_TIMESTAMP_KEY, v) - mergedUrl.SetParam(constant.TIMESTAMP_KEY, referenceUrl.GetParam(constant.TIMESTAMP_KEY, "")) + params[constant.REMOTE_TIMESTAMP_KEY] = []string{v} + params[constant.TIMESTAMP_KEY] = []string{referenceUrl.GetParam(constant.TIMESTAMP_KEY, "")} } // finally execute methodConfigMergeFcn @@ -592,7 +672,8 @@ func MergeUrl(serviceUrl *URL, referenceUrl *URL) *URL { fcn("methods." + method) } } - + // In this way, we will raise some performance. + mergedUrl.ReplaceParams(params) return mergedUrl } @@ -621,6 +702,19 @@ func (c *URL) CloneExceptParams(excludeParams *gxset.HashSet) *URL { return newUrl } +func (c *URL) Compare(comp cm.Comparator) int { + a := c.String() + b := comp.(*URL).String() + switch { + case a > b: + return 1 + case a < b: + return -1 + default: + return 0 + } +} + // Copy url based on the reserved parameter's keys. func (c *URL) CloneWithParams(reserveParams []string) *URL { params := url.Values{} @@ -643,15 +737,46 @@ func (c *URL) CloneWithParams(reserveParams []string) *URL { ) } -func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []func(method string) { +// IsEquals compares if two URLs equals with each other. Excludes are all parameter keys which should ignored. +func IsEquals(left *URL, right *URL, excludes ...string) bool { + if (left == nil && right != nil) || (right == nil && left != nil) { + return false + } + if left.Ip != right.Ip || left.Port != right.Port { + return false + } + + leftMap := left.ToMap() + rightMap := right.ToMap() + for _, exclude := range excludes { + delete(leftMap, exclude) + delete(rightMap, exclude) + } + + if len(leftMap) != len(rightMap) { + return false + } + + for lk, lv := range leftMap { + if rv, ok := rightMap[lk]; !ok { + return false + } else if lv != rv { + return false + } + } + + return true +} + +func mergeNormalParam(params url.Values, referenceUrl *URL, paramKeys []string) []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) + params[paramKey] = []string{v} } methodConfigMergeFcn = append(methodConfigMergeFcn, func(method string) { if v := referenceUrl.GetParam(method+"."+paramKey, ""); len(v) > 0 { - mergedUrl.SetParam(method+"."+paramKey, v) + params[method+"."+paramKey] = []string{v} } }) } @@ -660,7 +785,7 @@ func mergeNormalParam(mergedUrl *URL, referenceUrl *URL, paramKeys []string) []f // URLSlice will be used to sort URL instance // Instances will be order by URL.String() -type URLSlice []URL +type URLSlice []*URL // nolint func (s URLSlice) Len() int { @@ -676,3 +801,17 @@ func (s URLSlice) Less(i, j int) bool { func (s URLSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type CompareURLEqualFunc func(l *URL, r *URL, excludeParam ...string) bool + +func defaultCompareURLEqual(l *URL, r *URL, excludeParam ...string) bool { + return IsEquals(l, r, excludeParam...) +} + +func SetCompareURLEqualFunc(f CompareURLEqualFunc) { + compareURLEqualFunc = f +} + +func GetCompareURLEqualFunc() CompareURLEqualFunc { + return compareURLEqualFunc +} diff --git a/common/url_test.go b/common/url_test.go index 6845190a7362571ebbd4738bd146c94f6d644253..9f413494c685ecb908e102aa5e8181fd473445e0 100644 --- a/common/url_test.go +++ b/common/url_test.go @@ -167,10 +167,22 @@ func TestURLGetParam(t *testing.T) { func TestURLGetParamInt(t *testing.T) { params := url.Values{} - params.Set("key", "3") + params.Set("key", "") u := URL{baseUrl: baseUrl{params: params}} v := u.GetParamInt("key", 1) - assert.Equal(t, int64(3), v) + assert.Equal(t, int64(1), v) + + u = URL{} + v = u.GetParamInt("key", 1) + assert.Equal(t, int64(1), v) +} + +func TestURLGetParamIntValue(t *testing.T) { + params := url.Values{} + params.Set("key", "0") + u := URL{baseUrl: baseUrl{params: params}} + v := u.GetParamInt("key", 1) + assert.Equal(t, int64(0), v) u = URL{} v = u.GetParamInt("key", 1) @@ -277,7 +289,7 @@ func TestMergeUrl(t *testing.T) { referenceUrl, _ := NewURL("mock1://127.0.0.1:1111", WithParams(referenceUrlParams), WithMethods([]string{"testMethod"})) serviceUrl, _ := NewURL("mock2://127.0.0.1:20000", WithParams(serviceUrlParams)) - mergedUrl := MergeUrl(&serviceUrl, &referenceUrl) + mergedUrl := MergeUrl(serviceUrl, referenceUrl) assert.Equal(t, "random", mergedUrl.GetParam(constant.CLUSTER_KEY, "")) assert.Equal(t, "1", mergedUrl.GetParam("test2", "")) assert.Equal(t, "1", mergedUrl.GetParam("test3", "")) @@ -295,6 +307,16 @@ func TestURLSetParams(t *testing.T) { assert.Equal(t, "2.6.0", u1.GetParam("version", "")) } +func TestURLReplaceParams(t *testing.T) { + u1, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&configVersion=1.0") + assert.NoError(t, err) + params := url.Values{} + params.Set("key", "3") + u1.ReplaceParams(params) + assert.Equal(t, "3", u1.GetParam("key", "")) + assert.Equal(t, "", u1.GetParam("version", "")) +} + func TestClone(t *testing.T) { u1, err := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=&version=2.6.0&configVersion=1.0") assert.NoError(t, err) @@ -317,5 +339,56 @@ func TestColonSeparatedKey(t *testing.T) { assert.Equal(t, u1.ColonSeparatedKey(), u1.GetParam(constant.INTERFACE_KEY, "")+":version1:group1") u1.SetParam(constant.VERSION_KEY, "") assert.Equal(t, u1.ColonSeparatedKey(), u1.GetParam(constant.INTERFACE_KEY, "")+"::group1") +} + +func TestCompareURLEqualFunc(t *testing.T) { + // test Default + url1, _ := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245") + url2, _ := NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=155650979798") + assert.False(t, GetCompareURLEqualFunc()(url1, url2)) + assert.True(t, GetCompareURLEqualFunc()(url1, url2, constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY)) + + // test custom + url1, _ = NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245") + url2, _ = NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=155650979798") + assert.True(t, GetCompareURLEqualFunc()(url1, url2, constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY)) + SetCompareURLEqualFunc(CustomCompareURLEqual) + assert.False(t, GetCompareURLEqualFunc()(url1, url2)) + assert.False(t, GetCompareURLEqualFunc()(url1, url2, constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY)) + + url1, _ = NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000") + url2, _ = NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000") + assert.True(t, GetCompareURLEqualFunc()(url1, url2)) + assert.True(t, GetCompareURLEqualFunc()(url1, url2, constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY)) + SetCompareURLEqualFunc(CustomCompareURLEqual) + assert.True(t, GetCompareURLEqualFunc()(url1, url2)) + assert.True(t, GetCompareURLEqualFunc()(url1, url2, constant.TIMESTAMP_KEY, constant.REMOTE_TIMESTAMP_KEY)) +} +func CustomCompareURLEqual(l *URL, r *URL, execludeParam ...string) bool { + return l.PrimitiveURL == r.PrimitiveURL } diff --git a/config/application_config.go b/config/application_config.go index ef99664fa298c28365ed7acc54d0c18a88c9b5c2..6fe6c5d049f8ce95a56fcfd62eb1cffc03bf3c43 100644 --- a/config/application_config.go +++ b/config/application_config.go @@ -48,8 +48,5 @@ func (c *ApplicationConfig) UnmarshalYAML(unmarshal func(interface{}) error) err return err } type plain ApplicationConfig - if err := unmarshal((*plain)(c)); err != nil { - return err - } - return nil + return unmarshal((*plain)(c)) } diff --git a/config/base_config.go b/config/base_config.go index 22a0832731daff6c9957d4913a3784c9b268b11f..0cc6eec26c51cb7dfc164a3d43545f6b22658ca0 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -33,10 +33,6 @@ import ( "github.com/apache/dubbo-go/common/logger" ) -type multiConfiger interface { - Prefix() string -} - // BaseConfig is the common configuration for provider and consumer type BaseConfig struct { ConfigCenterConfig *ConfigCenterConfig `yaml:"config_center" json:"config_center,omitempty"` @@ -78,15 +74,8 @@ func getKeyPrefix(val reflect.Value) []string { } else { prefix = val.MethodByName(configPrefixMethod).Call(nil)[0].String() } - var retPrefixes []string - - for _, pfx := range strings.Split(prefix, "|") { - - retPrefixes = append(retPrefixes, pfx) - - } - return retPrefixes + return strings.Split(prefix, "|") } func getPtrElement(v reflect.Value) reflect.Value { @@ -216,12 +205,9 @@ func setFieldValue(val reflect.Value, id reflect.Value, config *config.InmemoryC prefix := s.MethodByName("Prefix").Call(nil)[0].String() for _, pfx := range strings.Split(prefix, "|") { m := config.GetSubProperty(pfx) - if m != nil { - for k := range m { - f.SetMapIndex(reflect.ValueOf(k), reflect.New(f.Type().Elem().Elem())) - } + for k := range m { + f.SetMapIndex(reflect.ValueOf(k), reflect.New(f.Type().Elem().Elem())) } - } } diff --git a/config/config_center_config.go b/config/config_center_config.go index 0fc4007940d9b1ac2456c9b2d379493bb5d8edb0..3bb856415d77344658055f03ccc4a6edd8c0f48a 100644 --- a/config/config_center_config.go +++ b/config/config_center_config.go @@ -26,6 +26,7 @@ import ( import ( "github.com/creasty/defaults" + perrors "github.com/pkg/errors" ) import ( @@ -35,7 +36,6 @@ import ( "github.com/apache/dubbo-go/common/extension" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config_center" - perrors "github.com/pkg/errors" ) // ConfigCenterConfig is configuration for config center @@ -69,10 +69,7 @@ func (c *ConfigCenterConfig) UnmarshalYAML(unmarshal func(interface{}) error) er return err } type plain ConfigCenterConfig - if err := unmarshal((*plain)(c)); err != nil { - return err - } - return nil + return unmarshal((*plain)(c)) } // GetUrlMap gets url map from ConfigCenterConfig @@ -91,7 +88,7 @@ type configCenter struct { // toURL will compatible with baseConfig.ConfigCenterConfig.Address and baseConfig.ConfigCenterConfig.RemoteRef before 1.6.0 // After 1.6.0 will not compatible, only baseConfig.ConfigCenterConfig.RemoteRef -func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { +func (b *configCenter) toURL(baseConfig BaseConfig) (*common.URL, error) { if len(baseConfig.ConfigCenterConfig.Address) > 0 { return common.NewURL(baseConfig.ConfigCenterConfig.Address, common.WithProtocol(baseConfig.ConfigCenterConfig.Protocol), common.WithParams(baseConfig.ConfigCenterConfig.GetUrlMap())) @@ -101,7 +98,7 @@ func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { rc, ok := baseConfig.GetRemoteConfig(remoteRef) if !ok { - return common.URL{}, perrors.New("Could not find out the remote ref config, name: " + remoteRef) + return nil, perrors.New("Could not find out the remote ref config, name: " + remoteRef) } newURL, err := rc.toURL() @@ -114,11 +111,11 @@ func (b *configCenter) toURL(baseConfig BaseConfig) (common.URL, error) { // startConfigCenter will start the config center. // it will prepare the environment func (b *configCenter) startConfigCenter(baseConfig BaseConfig) error { - url, err := b.toURL(baseConfig) + newUrl, err := b.toURL(baseConfig) if err != nil { return err } - if err = b.prepareEnvironment(baseConfig, &url); err != nil { + if err = b.prepareEnvironment(baseConfig, newUrl); err != nil { return perrors.WithMessagef(err, "start config center error!") } // c.fresh() diff --git a/config/config_loader.go b/config/config_loader.go index 75b82628d68a23e575cfa637b3603d09e09ea9d6..ec591aad6526e5841656465e71465413d01ad5ba 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -28,7 +28,6 @@ import ( ) import ( - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) @@ -141,19 +140,18 @@ func loadConsumerConfig() { // wait for invoker is available, if wait over default 3s, then panic var count int - checkok := true for { + checkok := true 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() { + 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) + 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) } @@ -161,14 +159,13 @@ func loadConsumerConfig() { break } if refconfig.invoker == nil { - logger.Warnf("The interface %s invoker not exist , may you should check your interface config.", refconfig.InterfaceName) + logger.Warnf("The interface %s invoker not exist, may you should check your interface config.", refconfig.InterfaceName) } } } if checkok { break } - checkok = true } } @@ -219,7 +216,7 @@ func registerServiceInstance() { if url == nil { return } - instance, err := createInstance(*url) + instance, err := createInstance(url) if err != nil { panic(err) } @@ -243,7 +240,7 @@ func registerServiceInstance() { } // nolint -func createInstance(url common.URL) (registry.ServiceInstance, error) { +func createInstance(url *common.URL) (registry.ServiceInstance, error) { appConfig := GetApplicationConfig() port, err := strconv.ParseInt(url.Port, 10, 32) if err != nil { @@ -252,10 +249,7 @@ func createInstance(url common.URL) (registry.ServiceInstance, error) { 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") - } + host = common.GetLocalIp() } // usually we will add more metadata @@ -275,7 +269,7 @@ func createInstance(url common.URL) (registry.ServiceInstance, error) { // selectMetadataServiceExportedURL get already be exported url func selectMetadataServiceExportedURL() *common.URL { - var selectedUrl common.URL + var selectedUrl *common.URL metaDataService, err := extension.GetMetadataService(GetApplicationConfig().MetadataType) if err != nil { logger.Warn(err) @@ -300,7 +294,7 @@ func selectMetadataServiceExportedURL() *common.URL { break } } - return &selectedUrl + return selectedUrl } func initRouter() { diff --git a/config/config_loader_test.go b/config/config_loader_test.go index 461e607c1e0ad2e9471770224c8eb6d5f3ee96f6..c3c3eb93526a51fe8f2041b17049144b4ec7b703 100644 --- a/config/config_loader_test.go +++ b/config/config_loader_test.go @@ -25,7 +25,6 @@ import ( ) import ( - cm "github.com/Workiva/go-datastructures/common" "github.com/Workiva/go-datastructures/slice/skip" gxset "github.com/dubbogo/gost/container/set" gxpage "github.com/dubbogo/gost/page" @@ -105,7 +104,7 @@ func TestLoad(t *testing.T) { conServices = map[string]common.RPCService{} proServices = map[string]common.RPCService{} - err := common.ServiceMap.UnRegister("com.MockService", "mock", "MockService") + err := common.ServiceMap.UnRegister("com.MockService", "mock", common.ServiceKey("com.MockService", "huadong_idc", "1.0.0")) assert.Nil(t, err) consumerConfig = nil providerConfig = nil @@ -144,7 +143,7 @@ func TestLoadWithSingleReg(t *testing.T) { conServices = map[string]common.RPCService{} proServices = map[string]common.RPCService{} - common.ServiceMap.UnRegister("com.MockService", "mock", "MockService") + common.ServiceMap.UnRegister("com.MockService", "mock", common.ServiceKey("com.MockService", "huadong_idc", "1.0.0")) consumerConfig = nil providerConfig = nil } @@ -183,7 +182,7 @@ func TestWithNoRegLoad(t *testing.T) { conServices = map[string]common.RPCService{} proServices = map[string]common.RPCService{} - common.ServiceMap.UnRegister("com.MockService", "mock", "MockService") + common.ServiceMap.UnRegister("com.MockService", "mock", common.ServiceKey("com.MockService", "huadong_idc", "1.0.0")) consumerConfig = nil providerConfig = nil } @@ -353,27 +352,27 @@ func (m *mockMetadataService) ServiceName() (string, error) { panic("implement me") } -func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) { - return m.addURL(m.exportedServiceURLs, &url), nil +func (m *mockMetadataService) ExportURL(url *common.URL) (bool, error) { + return m.addURL(m.exportedServiceURLs, url), nil } -func (m *mockMetadataService) UnexportURL(url common.URL) error { +func (m *mockMetadataService) UnexportURL(*common.URL) error { panic("implement me") } -func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) { +func (m *mockMetadataService) SubscribeURL(*common.URL) (bool, error) { panic("implement me") } -func (m *mockMetadataService) UnsubscribeURL(url common.URL) error { +func (m *mockMetadataService) UnsubscribeURL(*common.URL) error { panic("implement me") } -func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error { +func (m *mockMetadataService) PublishServiceDefinition(*common.URL) error { return nil } -func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { +func (m *mockMetadataService) GetExportedURLs(string, string, string, string) ([]interface{}, error) { return ConvertURLArrToIntfArr(m.getAllService(m.exportedServiceURLs)), nil } @@ -381,19 +380,19 @@ func (m *mockMetadataService) MethodMapper() map[string]string { panic("implement me") } -func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { +func (m *mockMetadataService) GetSubscribedURLs() ([]*common.URL, error) { panic("implement me") } -func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { +func (m *mockMetadataService) GetServiceDefinition(string, string, string) (string, error) { panic("implement me") } -func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { +func (m *mockMetadataService) GetServiceDefinitionByServiceKey(string) (string, error) { panic("implement me") } -func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { +func (m *mockMetadataService) RefreshMetadata(string, string) (bool, error) { panic("implement me") } @@ -409,7 +408,7 @@ func (mts *mockMetadataService) addURL(targetMap *sync.Map, url *common.URL) boo 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)) + wantedUrl := urlSet.(*skip.SkipList).Get(url) if len(wantedUrl) > 0 && wantedUrl[0] != nil { mts.lock.RUnlock() return false @@ -418,23 +417,23 @@ func (mts *mockMetadataService) addURL(targetMap *sync.Map, url *common.URL) boo } mts.lock.Lock() // double chk - wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + wantedUrl := urlSet.(*skip.SkipList).Get(url) if len(wantedUrl) > 0 && wantedUrl[0] != nil { mts.lock.Unlock() return false } - urlSet.(*skip.SkipList).Insert(Comparator(*url)) + urlSet.(*skip.SkipList).Insert(url) mts.lock.Unlock() return true } -func (m *mockMetadataService) getAllService(services *sync.Map) []common.URL { +func (m *mockMetadataService) getAllService(services *sync.Map) []*common.URL { // using skip list to dedup and sorting - res := make([]common.URL, 0) + var res []*common.URL 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)) + url := urls.ByPosition(i).(*common.URL) if url.GetParam(constant.INTERFACE_KEY, url.Path) != constant.METADATA_SERVICE_NAME { res = append(res, url) } @@ -445,26 +444,10 @@ func (m *mockMetadataService) getAllService(services *sync.Map) []common.URL { return res } -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 - } -} - type mockServiceDiscoveryRegistry struct { } -func (mr *mockServiceDiscoveryRegistry) GetUrl() common.URL { +func (mr *mockServiceDiscoveryRegistry) GetUrl() *common.URL { panic("implement me") } @@ -476,11 +459,11 @@ func (mr *mockServiceDiscoveryRegistry) Destroy() { panic("implement me") } -func (mr *mockServiceDiscoveryRegistry) Register(url common.URL) error { +func (mr *mockServiceDiscoveryRegistry) Register(*common.URL) error { panic("implement me") } -func (mr *mockServiceDiscoveryRegistry) UnRegister(url common.URL) error { +func (mr *mockServiceDiscoveryRegistry) UnRegister(*common.URL) error { panic("implement me") } @@ -507,15 +490,15 @@ func (m *mockServiceDiscovery) Destroy() error { panic("implement me") } -func (m *mockServiceDiscovery) Register(instance registry.ServiceInstance) error { +func (m *mockServiceDiscovery) Register(registry.ServiceInstance) error { return nil } -func (m *mockServiceDiscovery) Update(instance registry.ServiceInstance) error { +func (m *mockServiceDiscovery) Update(registry.ServiceInstance) error { panic("implement me") } -func (m *mockServiceDiscovery) Unregister(instance registry.ServiceInstance) error { +func (m *mockServiceDiscovery) Unregister(registry.ServiceInstance) error { panic("implement me") } @@ -527,39 +510,39 @@ func (m *mockServiceDiscovery) GetServices() *gxset.HashSet { panic("implement me") } -func (m *mockServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { +func (m *mockServiceDiscovery) GetInstances(string) []registry.ServiceInstance { panic("implement me") } -func (m *mockServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { +func (m *mockServiceDiscovery) GetInstancesByPage(string, int, int) gxpage.Pager { panic("implement me") } -func (m *mockServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { +func (m *mockServiceDiscovery) GetHealthyInstancesByPage(string, int, int, bool) gxpage.Pager { panic("implement me") } -func (m *mockServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { +func (m *mockServiceDiscovery) GetRequestInstances([]string, int, int) map[string]gxpage.Pager { panic("implement me") } -func (m *mockServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { +func (m *mockServiceDiscovery) AddListener(*registry.ServiceInstancesChangedListener) error { panic("implement me") } -func (m *mockServiceDiscovery) DispatchEventByServiceName(serviceName string) error { +func (m *mockServiceDiscovery) DispatchEventByServiceName(string) error { panic("implement me") } -func (m *mockServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { +func (m *mockServiceDiscovery) DispatchEventForInstances(string, []registry.ServiceInstance) error { panic("implement me") } -func (m *mockServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { +func (m *mockServiceDiscovery) DispatchEvent(*registry.ServiceInstancesChangedEvent) error { panic("implement me") } -func ConvertURLArrToIntfArr(urls []common.URL) []interface{} { +func ConvertURLArrToIntfArr(urls []*common.URL) []interface{} { if len(urls) == 0 { return []interface{}{} } diff --git a/config/consumer_config.go b/config/consumer_config.go index 9d283eeca7bbaf5a82f71357853c6b53560b2fe4..ff53366c45406dde8e4c0daac7a5d68aa6bd8934 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -60,8 +60,8 @@ type ConsumerConfig struct { References map[string]*ReferenceConfig `yaml:"references" json:"references,omitempty" property:"references"` ProtocolConf interface{} `yaml:"protocol_conf" json:"protocol_conf,omitempty" property:"protocol_conf"` - FilterConf interface{} `yaml:"filter_conf" json:"filter_conf,omitempty" property:"filter_conf" ` - ShutdownConfig *ShutdownConfig `yaml:"shutdown_conf" json:"shutdown_conf,omitempty" property:"shutdown_conf" ` + 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"` } @@ -71,10 +71,7 @@ func (c *ConsumerConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return err } type plain ConsumerConfig - if err := unmarshal((*plain)(c)); err != nil { - return err - } - return nil + return unmarshal((*plain)(c)) } // nolint diff --git a/config/instance/metadata_report.go b/config/instance/metadata_report.go index 8e833dd70bcc0db8e65cd8703f2bc1859432a887..68a197d3ddb4abfd8ab924749857a7f94a43e1d4 100644 --- a/config/instance/metadata_report.go +++ b/config/instance/metadata_report.go @@ -29,7 +29,7 @@ import ( var ( instance report.MetadataReport - reportUrl common.URL + reportUrl *common.URL once sync.Once ) @@ -41,18 +41,18 @@ func GetMetadataReportInstance(selectiveUrl ...*common.URL) report.MetadataRepor if len(selectiveUrl) > 0 { url = selectiveUrl[0] instance = extension.GetMetadataReportFactory(url.Protocol).CreateMetadataReport(url) - reportUrl = *url + reportUrl = url } }) return instance } // GetMetadataReportUrl will return the report instance url -func GetMetadataReportUrl() common.URL { +func GetMetadataReportUrl() *common.URL { return reportUrl } // SetMetadataReportUrl will only can be used by unit test to mock url -func SetMetadataReportUrl(url common.URL) { +func SetMetadataReportUrl(url *common.URL) { reportUrl = url } diff --git a/config/instance/metadata_report_test.go b/config/instance/metadata_report_test.go index d489af048055b362f2fa68a8963dd2cdf9632945..110903a41f577ef78b4d7abbcef17864fd6e281c 100644 --- a/config/instance/metadata_report_test.go +++ b/config/instance/metadata_report_test.go @@ -38,7 +38,7 @@ func TestGetMetadataReportInstance(t *testing.T) { return &mockMetadataReportFactory{} }) u, _ := common.NewURL("mock://127.0.0.1") - rpt := GetMetadataReportInstance(&u) + rpt := GetMetadataReportInstance(u) assert.NotNil(t, rpt) } @@ -60,7 +60,7 @@ func (m mockMetadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier panic("implement me") } -func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error { +func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, *common.URL) error { panic("implement me") } diff --git a/config/interfaces/config_post_processor.go b/config/interfaces/config_post_processor.go new file mode 100644 index 0000000000000000000000000000000000000000..53dd71780ff0807065e8c4b0e00ab4fe8b6a4823 --- /dev/null +++ b/config/interfaces/config_post_processor.go @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package interfaces + +import ( + "github.com/apache/dubbo-go/common" +) + +// ConfigPostProcessor is an extension to give users a chance to customize configs against ReferenceConfig and +// ServiceConfig during deployment time. +type ConfigPostProcessor interface { + // PostProcessReferenceConfig customizes ReferenceConfig's params. + PostProcessReferenceConfig(*common.URL) + + // PostProcessServiceConfig customizes ServiceConfig's params. + PostProcessServiceConfig(*common.URL) +} diff --git a/config/metadata_report_config.go b/config/metadata_report_config.go index 6d319e5ecb8007e06dcf790fff145bfab754df3d..24eba5ec8dc483966b8e63271b40cde9cbf54e3e 100644 --- a/config/metadata_report_config.go +++ b/config/metadata_report_config.go @@ -84,7 +84,7 @@ func (c *MetadataReportConfig) ToUrl() (*common.URL, error) { return nil, perrors.New("Invalid MetadataReportConfig.") } res.SetParam("metadata", res.Protocol) - return &res, nil + return res, nil } func (c *MetadataReportConfig) IsValid() bool { @@ -101,8 +101,8 @@ func startMetadataReport(metadataType string, metadataReportConfig *MetadataRepo return perrors.New("MetadataConfig remote ref can not be empty.") } - if url, err := metadataReportConfig.ToUrl(); err == nil { - instance.GetMetadataReportInstance(url) + if tmpUrl, err := metadataReportConfig.ToUrl(); err == nil { + instance.GetMetadataReportInstance(tmpUrl) } else { return perrors.Wrap(err, "Start MetadataReport failed.") } diff --git a/config/method_config.go b/config/method_config.go index b64306fd6aa865d219506ea2722067619b00fea7..db52940c3db6b4b81b0467945b538c9b540cd46a 100644 --- a/config/method_config.go +++ b/config/method_config.go @@ -57,8 +57,5 @@ func (c *MethodConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } type plain MethodConfig - if err := unmarshal((*plain)(c)); err != nil { - return err - } - return nil + return unmarshal((*plain)(c)) } diff --git a/config/provider_config.go b/config/provider_config.go index c710e48dc233a62837b31a89828e9c612eaff093..0aee5600298233b764df75b02c745a3ec8d9faef 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -43,9 +43,9 @@ type ProviderConfig struct { 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" ` + 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"` @@ -58,10 +58,7 @@ func (c *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return err } type plain ProviderConfig - if err := unmarshal((*plain)(c)); err != nil { - return err - } - return nil + return unmarshal((*plain)(c)) } // nolint diff --git a/config/reference_config.go b/config/reference_config.go index bbc875192c7a87354ccc81e28ea05bbc3bb71149..431ec0e2eb3c03b27cca40acd7b721cf6b8f9755 100644 --- a/config/reference_config.go +++ b/config/reference_config.go @@ -86,17 +86,13 @@ func (c *ReferenceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error } *c = ReferenceConfig(raw) - if err := defaults.Set(c); err != nil { - return err - } - - return nil + return defaults.Set(c) } // Refer ... func (c *ReferenceConfig) Refer(_ interface{}) { cfgURL := common.NewURLWithOptions( - common.WithPath(c.id), + common.WithPath(c.InterfaceName), common.WithProtocol(c.Protocol), common.WithParams(c.getUrlMap()), common.WithParamsValue(constant.BEAN_NAME_KEY, c.id), @@ -104,6 +100,9 @@ func (c *ReferenceConfig) Refer(_ interface{}) { if c.ForceTag { cfgURL.AddParam(constant.ForceUseTag, "true") } + + c.postProcessConfig(cfgURL) + 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*") @@ -114,13 +113,13 @@ func (c *ReferenceConfig) Refer(_ interface{}) { } if serviceUrl.Protocol == constant.REGISTRY_PROTOCOL { serviceUrl.SubURL = cfgURL - c.urls = append(c.urls, &serviceUrl) + c.urls = append(c.urls, serviceUrl) } else { if serviceUrl.Path == "" { - serviceUrl.Path = "/" + c.id + serviceUrl.Path = "/" + c.InterfaceName } // merge url need to do - newUrl := common.MergeUrl(&serviceUrl, cfgURL) + newUrl := common.MergeUrl(serviceUrl, cfgURL) c.urls = append(c.urls, newUrl) } } @@ -135,12 +134,12 @@ func (c *ReferenceConfig) Refer(_ interface{}) { } if len(c.urls) == 1 { - c.invoker = extension.GetProtocol(c.urls[0].Protocol).Refer(*c.urls[0]) + c.invoker = extension.GetProtocol(c.urls[0].Protocol).Refer(c.urls[0]) } else { invokers := make([]protocol.Invoker, 0, len(c.urls)) var regUrl *common.URL for _, u := range c.urls { - invokers = append(invokers, extension.GetProtocol(u.Protocol).Refer(*u)) + invokers = append(invokers, extension.GetProtocol(u.Protocol).Refer(u)) if u.Protocol == constant.REGISTRY_PROTOCOL { regUrl = u } @@ -189,6 +188,11 @@ func (c *ReferenceConfig) GetRPCService() common.RPCService { return c.pxy.Get() } +// GetProxy gets proxy +func (c *ReferenceConfig) GetProxy() *proxy.Proxy { + return c.pxy +} + func (c *ReferenceConfig) getUrlMap() url.Values { urlMap := url.Values{} //first set user params @@ -251,5 +255,11 @@ func (c *ReferenceConfig) GenericLoad(id string) { c.id = id c.Refer(genericService) c.Implement(genericService) - return +} + +// postProcessConfig asks registered ConfigPostProcessor to post-process the current ReferenceConfig. +func (c *ReferenceConfig) postProcessConfig(url *common.URL) { + for _, p := range extension.GetConfigPostProcessors() { + p.PostProcessReferenceConfig(url) + } } diff --git a/config/reference_config_test.go b/config/reference_config_test.go index a4345ad13d4944b8fae0930930584fccbd0fb33c..0207e1ff24af022690e3773573ddd61ebad88e15 100644 --- a/config/reference_config_test.go +++ b/config/reference_config_test.go @@ -334,7 +334,7 @@ func newRegistryProtocol() protocol.Protocol { type mockRegistryProtocol struct{} -func (*mockRegistryProtocol) Refer(url common.URL) protocol.Invoker { +func (*mockRegistryProtocol) Refer(url *common.URL) protocol.Invoker { return protocol.NewBaseInvoker(url) } @@ -345,7 +345,7 @@ func (*mockRegistryProtocol) Export(invoker protocol.Invoker) protocol.Exporter if err != nil { panic(err) } - ok, err := metaDataService.ExportURL(*invoker.GetUrl().SubURL.Clone()) + ok, err := metaDataService.ExportURL(invoker.GetUrl().SubURL.Clone()) if err != nil { panic(err) } @@ -367,7 +367,7 @@ func getRegistryUrl(invoker protocol.Invoker) *common.URL { protocol := url.GetParam(constant.REGISTRY_KEY, "") url.Protocol = protocol } - return &url + return url } func (p *mockRegistryProtocol) GetRegistries() []registry.Registry { diff --git a/config/registry_config.go b/config/registry_config.go index 89566c428ed14f460c0f214358c9fa05d529ddb6..ed81a07c637e5de2972bbbd21dab8847c3d42f78 100644 --- a/config/registry_config.go +++ b/config/registry_config.go @@ -53,7 +53,7 @@ type RegistryConfig struct { //ZoneForce bool `yaml:"zoneForce" json:"zoneForce,omitempty" property:"zoneForce"` // Affects traffic distribution among registries, // useful when subscribe to multiple registries Take effect only when no preferred registry is specified. - Weight int64 `yaml:"weight" json:"params,omitempty" property:"weight"` + Weight int64 `yaml:"weight" json:"weight,omitempty" property:"weight"` Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` } @@ -63,10 +63,7 @@ func (c *RegistryConfig) UnmarshalYAML(unmarshal func(interface{}) error) error return err } type plain RegistryConfig - if err := unmarshal((*plain)(c)); err != nil { - return err - } - return nil + return unmarshal((*plain)(c)) } // nolint @@ -114,7 +111,7 @@ func loadRegistries(targetRegistries string, registries map[string]*RegistryConf logger.Errorf("The registry id: %s url is invalid, error: %#v", k, err) panic(err) } else { - urls = append(urls, &url) + urls = append(urls, url) } } } diff --git a/config/remote_config.go b/config/remote_config.go index 55380dd5a05b47b5b4677b32daf73b37376673d0..61d4dce0cdc104d6f9710d1a1f3caa540edec076 100644 --- a/config/remote_config.go +++ b/config/remote_config.go @@ -40,7 +40,7 @@ type RemoteConfig struct { 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"` + Params map[string]string `yaml:"params" json:"params,omitempty"` } // Timeout return timeout duration. @@ -63,9 +63,9 @@ func (rc *RemoteConfig) GetParam(key string, def string) string { return param } -func (rc *RemoteConfig) toURL() (common.URL, error) { +func (rc *RemoteConfig) toURL() (*common.URL, error) { if len(rc.Protocol) == 0 { - return common.URL{}, perrors.Errorf("Must provide protocol in RemoteConfig.") + return nil, perrors.Errorf("Must provide protocol in RemoteConfig.") } return common.NewURL(rc.Address, common.WithUsername(rc.Username), diff --git a/config/router_config.go b/config/router_config.go index ed42577ed3cce2e5a1ab0da290f0d5450553d8fb..ea19b46800d747de763bbb1ee679f8398e440f3d 100644 --- a/config/router_config.go +++ b/config/router_config.go @@ -66,7 +66,7 @@ func initRouterConfig(content []byte, factories map[string]router.FilePriorityRo r, e := factory.NewFileRouter(content) if e == nil { url := r.URL() - routerURLSet.Add(&url) + routerURLSet.Add(url) return nil } logger.Warnf("router config type %s create fail {%v}\n", k, e) diff --git a/config/service_config.go b/config/service_config.go index 54383e4791bf0d0749aea08d7ba9a613e4cfe70b..087f537b5937821fa39c042f21feb66792e22502 100644 --- a/config/service_config.go +++ b/config/service_config.go @@ -59,6 +59,7 @@ type ServiceConfig struct { Methods []*MethodConfig `yaml:"methods" json:"methods,omitempty" property:"methods"` Warmup string `yaml:"warmup" json:"warmup,omitempty" property:"warmup"` Retries string `yaml:"retries" json:"retries,omitempty" property:"retries"` + Serialization string `yaml:"serialization" json:"serialization" property:"serialization"` Params map[string]string `yaml:"params" json:"params,omitempty" property:"params"` Token string `yaml:"token" json:"token,omitempty" property:"token"` AccessLog string `yaml:"accesslog" json:"accesslog,omitempty" property:"accesslog"` @@ -72,10 +73,12 @@ type ServiceConfig struct { 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"` + GrpcMaxMessageSize int `default:"4" yaml:"max_message_size" json:"max_message_size,omitempty"` Protocols map[string]*ProtocolConfig unexported *atomic.Bool exported *atomic.Bool + export bool // a flag to control whether the current service should export or not rpcService common.RPCService cacheMutex sync.Mutex cacheProtocol protocol.Protocol @@ -100,6 +103,7 @@ func (c *ServiceConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { } c.exported = atomic.NewBool(false) c.unexported = atomic.NewBool(false) + c.export = true return nil } @@ -110,6 +114,7 @@ func NewServiceConfig(id string, context context.Context) *ServiceConfig { id: id, unexported: atomic.NewBool(false), exported: atomic.NewBool(false), + export: true, } } @@ -133,7 +138,7 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List { tcp, err := gxnet.ListenOnTCPRandomPort(proto.Ip) if err != nil { - panic(perrors.New(fmt.Sprintf("Get tcp port error,err is {%v}", err))) + panic(perrors.New(fmt.Sprintf("Get tcp port error, err is {%v}", err))) } defer tcp.Close() ports.PushBack(strings.Split(tcp.Addr().String(), ":")[1]) @@ -145,14 +150,14 @@ func getRandomPort(protocolConfigs []*ProtocolConfig) *list.List { func (c *ServiceConfig) Export() error { // TODO: config center start here - // TODO:delay export + // TODO: delay export if c.unexported != nil && c.unexported.Load() { - err := perrors.Errorf("The service %v has already unexported! ", c.InterfaceName) + err := perrors.Errorf("The service %v has already unexported!", c.InterfaceName) logger.Errorf(err.Error()) return err } if c.unexported != nil && c.exported.Load() { - logger.Warnf("The service %v has already exported! ", c.InterfaceName) + logger.Warnf("The service %v has already exported!", c.InterfaceName) return nil } @@ -160,29 +165,29 @@ func (c *ServiceConfig) Export() error { urlMap := c.getUrlMap() 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) + 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() + proxyFactory := extension.GetProxyFactory(providerConfig.ProxyFactory) for _, proto := range protocolConfigs { // registry the service reflect - methods, err := common.ServiceMap.Register(c.InterfaceName, proto.Name, c.rpcService) + methods, err := common.ServiceMap.Register(c.InterfaceName, proto.Name, c.Group, c.Version, c.rpcService) if err != nil { - formatErr := perrors.Errorf("The service %v export the protocol %v error! Error message is %v .", c.InterfaceName, proto.Name, err.Error()) + 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.WithPath(c.InterfaceName), common.WithProtocol(proto.Name), common.WithIp(proto.Ip), common.WithPort(port), @@ -196,33 +201,38 @@ func (c *ServiceConfig) Export() error { ivkURL.AddParam(constant.Tagkey, c.Tag) } - var exporter protocol.Exporter + // post process the URL to be exported + c.postProcessConfig(ivkURL) + // config post processor may set "export" to false + if !ivkURL.GetParamBool(constant.EXPORT_KEY, true) { + return nil + } if len(regUrls) > 0 { + c.cacheMutex.Lock() + if c.cacheProtocol == nil { + logger.Infof(fmt.Sprintf("First load the registry protocol, url is {%v}!", ivkURL)) + c.cacheProtocol = extension.GetProtocol("registry") + } + c.cacheMutex.Unlock() + for _, regUrl := range regUrls { regUrl.SubURL = ivkURL - - c.cacheMutex.Lock() - if c.cacheProtocol == nil { - logger.Infof(fmt.Sprintf("First load the registry protocol , url is {%v}!", ivkURL)) - c.cacheProtocol = extension.GetProtocol("registry") - } - c.cacheMutex.Unlock() - - invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl) - exporter = c.cacheProtocol.Export(invoker) + invoker := proxyFactory.GetInvoker(regUrl) + 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))) + return perrors.New(fmt.Sprintf("Registry protocol new exporter error, registry is {%v}, url is {%v}", regUrl, ivkURL)) } + c.exporters = append(c.exporters, exporter) } } else { - invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*ivkURL) - exporter = extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) + invoker := proxyFactory.GetInvoker(ivkURL) + exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) if exporter == nil { - panic(perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error,url is {%v}", ivkURL))) + return perrors.New(fmt.Sprintf("Filter protocol without registry new exporter error, url is {%v}", ivkURL)) } + c.exporters = append(c.exporters, exporter) } - c.exporters = append(c.exporters, exporter) } c.exported.Store(true) return nil @@ -272,7 +282,9 @@ func (c *ServiceConfig) getUrlMap() url.Values { 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()) - + urlMap.Set(constant.MESSAGE_SIZE_KEY, strconv.Itoa(c.GrpcMaxMessageSize)) + // todo: move + urlMap.Set(constant.SERIALIZATION_KEY, c.Serialization) // application info urlMap.Set(constant.APPLICATION_KEY, providerConfig.ApplicationConfig.Name) urlMap.Set(constant.ORGANIZATION_KEY, providerConfig.ApplicationConfig.Organization) @@ -302,6 +314,9 @@ func (c *ServiceConfig) getUrlMap() url.Values { urlMap.Set(constant.SERVICE_AUTH_KEY, c.Auth) urlMap.Set(constant.PARAMTER_SIGNATURE_ENABLE_KEY, c.ParamSign) + // whether to export or not + urlMap.Set(constant.EXPORT_KEY, strconv.FormatBool(c.export)) + for _, v := range c.Methods { prefix := "methods." + v.Name + "." urlMap.Set(prefix+constant.LOADBALANCE_KEY, v.LoadBalance) @@ -314,7 +329,6 @@ func (c *ServiceConfig) getUrlMap() url.Values { urlMap.Set(constant.EXECUTE_LIMIT_KEY, v.ExecuteLimit) urlMap.Set(constant.EXECUTE_REJECTED_EXECUTION_HANDLER_KEY, v.ExecuteLimitRejectedHandler) - } return urlMap @@ -325,10 +339,16 @@ 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) + urls = append(urls, exporter.GetInvoker().GetUrl()) } return urls } return nil } + +// postProcessConfig asks registered ConfigPostProcessor to post-process the current ServiceConfig. +func (c *ServiceConfig) postProcessConfig(url *common.URL) { + for _, p := range extension.GetConfigPostProcessors() { + p.PostProcessServiceConfig(url) + } +} diff --git a/config/service_config_test.go b/config/service_config_test.go index 4d4122ee7057043af47aa0400ca8c5b5e9a20cd0..61c8864b6ab7eb146a92e76187cd6363bbdb8dc8 100644 --- a/config/service_config_test.go +++ b/config/service_config_test.go @@ -18,11 +18,11 @@ package config import ( + "github.com/apache/dubbo-go/common" "testing" ) import ( - gxnet "github.com/dubbogo/gost/net" "github.com/stretchr/testify/assert" "go.uber.org/atomic" ) @@ -184,7 +184,7 @@ func TestExport(t *testing.T) { func TestGetRandomPort(t *testing.T) { protocolConfigs := make([]*ProtocolConfig, 0, 3) - ip, err := gxnet.GetLocalIP() + ip := common.GetLocalIp() protocolConfigs = append(protocolConfigs, &ProtocolConfig{ Ip: ip, }) @@ -194,7 +194,7 @@ func TestGetRandomPort(t *testing.T) { protocolConfigs = append(protocolConfigs, &ProtocolConfig{ Ip: ip, }) - assert.NoError(t, err) + //assert.NoError(t, err) ports := getRandomPort(protocolConfigs) assert.Equal(t, ports.Len(), len(protocolConfigs)) diff --git a/config_center/apollo/impl.go b/config_center/apollo/impl.go index 8030a2c800c67d47a27e7aaa5d6f1bb39a83cdc9..c69fc2f66b23cd877b447fc78084fb9197c253aa 100644 --- a/config_center/apollo/impl.go +++ b/config_center/apollo/impl.go @@ -45,6 +45,7 @@ const ( ) type apolloConfiguration struct { + cc.BaseDynamicConfiguration url *common.URL listeners sync.Map @@ -108,11 +109,11 @@ func getNamespaceName(namespace string, configFileFormat agolloConstant.ConfigFi } func (c *apolloConfiguration) GetInternalProperty(key string, opts ...cc.Option) (string, error) { - config := agollo.GetConfig(c.appConf.NamespaceName) - if config == nil { + newConfig := agollo.GetConfig(c.appConf.NamespaceName) + if newConfig == nil { return "", perrors.New(fmt.Sprintf("nothing in namespace:%s ", key)) } - return config.GetStringValue(key, ""), nil + return newConfig.GetStringValue(key, ""), nil } func (c *apolloConfiguration) GetRule(key string, opts ...cc.Option) (string, error) { @@ -134,11 +135,11 @@ func (c *apolloConfiguration) GetProperties(key string, opts ...cc.Option) (stri * when group is not null, we are getting startup configs(config file) from Config Center, for example: * key=dubbo.propertie */ - config := agollo.GetConfig(key) - if config == nil { + tmpConfig := agollo.GetConfig(key) + if tmpConfig == nil { return "", perrors.New(fmt.Sprintf("nothing in namespace:%s ", key)) } - return config.GetContent(), nil + return tmpConfig.GetContent(), nil } func (c *apolloConfiguration) getAddressWithProtocolPrefix(url *common.URL) string { diff --git a/config_center/apollo/impl_test.go b/config_center/apollo/impl_test.go index 50c4e689de4542e1c595d0778e2dce69b1e7dda9..4720775c285f84bd4fe2146240feea87c8a20817 100644 --- a/config_center/apollo/impl_test.go +++ b/config_center/apollo/impl_test.go @@ -197,7 +197,7 @@ func initMockApollo(t *testing.T) *apolloConfiguration { apolloUrl := strings.ReplaceAll(apollo.URL, "http", "apollo") url, err := common.NewURL(apolloUrl, common.WithParams(c.ConfigCenterConfig.GetUrlMap())) assert.NoError(t, err) - configuration, err := newApolloConfiguration(&url) + configuration, err := newApolloConfiguration(url) assert.NoError(t, err) return configuration } diff --git a/config_center/base_dynamic_configuration.go b/config_center/base_dynamic_configuration.go new file mode 100644 index 0000000000000000000000000000000000000000..3d6757852ad83d54338b721d0cb617772f40b6b7 --- /dev/null +++ b/config_center/base_dynamic_configuration.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 config_center + +// BaseDynamicConfiguration will default implementation DynamicConfiguration some method +type BaseDynamicConfiguration struct { +} + +// RemoveConfig +func (bdc *BaseDynamicConfiguration) RemoveConfig(string, string) error { + return nil +} diff --git a/config_center/configurator/override.go b/config_center/configurator/override.go index 294a60ebb2e4e18cfc47cd90aedeaa615b5626d2..7f5abb6175056b44a5d9c6a9fa527ad6e9ac6791 100644 --- a/config_center/configurator/override.go +++ b/config_center/configurator/override.go @@ -23,7 +23,6 @@ import ( import ( gxset "github.com/dubbogo/gost/container/set" - gxnet "github.com/dubbogo/gost/net" ) import ( @@ -61,7 +60,7 @@ func (c *overrideConfigurator) Configure(url *common.URL) { currentSide := url.GetParam(constant.SIDE_KEY, "") configuratorSide := c.configuratorUrl.GetParam(constant.SIDE_KEY, "") if currentSide == configuratorSide && common.DubboRole[common.CONSUMER] == currentSide && c.configuratorUrl.Port == "0" { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() c.configureIfMatch(localIP, url) } else if currentSide == configuratorSide && common.DubboRole[common.PROVIDER] == currentSide && c.configuratorUrl.Port == url.Port { c.configureIfMatch(url.Ip, url) @@ -110,7 +109,7 @@ func (c *overrideConfigurator) configureIfMatchInternal(url *common.URL) { 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, "") - if len(providers) == 0 || strings.Index(providers, url.Location) >= 0 || strings.Index(providers, constant.ANYHOST_VALUE) >= 0 { + if len(providers) == 0 || strings.Contains(providers, url.Location) || strings.Contains(providers, constant.ANYHOST_VALUE) { c.configureIfMatchInternal(url) } } @@ -127,7 +126,7 @@ func (c *overrideConfigurator) configureDeprecated(url *common.URL) { // 1.If it is a consumer ip address, the intention is to control a specific consumer instance, it must takes effect at the consumer side, any provider received this override url should ignore; // 2.If the ip is 0.0.0.0, this override url can be used on consumer, and also can be used on provider if url.GetParam(constant.SIDE_KEY, "") == common.DubboRole[common.CONSUMER] { - localIP, _ := gxnet.GetLocalIP() + localIP := common.GetLocalIp() c.configureIfMatch(localIP, url) } else { c.configureIfMatch(constant.ANYHOST_VALUE, url) diff --git a/config_center/configurator/override_test.go b/config_center/configurator/override_test.go index 8eccb5091272b033cf31b612dfb19bce6514ccce..bb9c36776337c5dde1c5b2dcc9dd59ee82067a02 100644 --- a/config_center/configurator/override_test.go +++ b/config_center/configurator/override_test.go @@ -40,36 +40,36 @@ const ( func TestConfigureVerison2p6(t *testing.T) { url, err := common.NewURL("override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService") assert.NoError(t, err) - configurator := extension.GetConfigurator(defaults, &url) + configurator := extension.GetConfigurator(defaults, url) assert.Equal(t, override, configurator.GetUrl().Protocol) providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") assert.NoError(t, err) - configurator.Configure(&providerUrl) + configurator.Configure(providerUrl) assert.Equal(t, failfast, providerUrl.GetParam(constant.CLUSTER_KEY, "")) } func TestConfigureVerisonOverrideAddr(t *testing.T) { url, err := common.NewURL("override://0.0.0.0:0/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&providerAddresses=127.0.0.2:20001|127.0.0.3:20001") assert.NoError(t, err) - configurator := extension.GetConfigurator(defaults, &url) + configurator := extension.GetConfigurator(defaults, url) assert.Equal(t, override, configurator.GetUrl().Protocol) providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") assert.NoError(t, err) - configurator.Configure(&providerUrl) + configurator.Configure(providerUrl) assert.Equal(t, failover, providerUrl.GetParam(constant.CLUSTER_KEY, "")) } func TestConfigureVerison2p6WithIp(t *testing.T) { url, err := common.NewURL("override://127.0.0.1:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService") assert.NoError(t, err) - configurator := extension.GetConfigurator(defaults, &url) + configurator := extension.GetConfigurator(defaults, url) assert.Equal(t, override, configurator.GetUrl().Protocol) providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") assert.NoError(t, err) - configurator.Configure(&providerUrl) + configurator.Configure(providerUrl) assert.Equal(t, failfast, providerUrl.GetParam(constant.CLUSTER_KEY, "")) } @@ -77,11 +77,11 @@ func TestConfigureVerison2p6WithIp(t *testing.T) { func TestConfigureVerison2p7(t *testing.T) { url, err := common.NewURL("jsonrpc://0.0.0.0:20001/com.xxx.mock.userProvider?group=1&version=1&cluster=failfast&application=BDTService&configVersion=1.0&side=provider") assert.NoError(t, err) - configurator := extension.GetConfigurator(defaults, &url) + configurator := extension.GetConfigurator(defaults, url) providerUrl, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&app.version=0.0.1&application=BDTService&category=providers&cluster=failover&dubbo=dubbo-provider-golang-2.6.0&environment=dev&group=&interface=com.ikurento.user.UserProvider&ip=10.32.20.124&loadbalance=random&methods.GetUser.loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name=BDTService&organization=ikurento.com&owner=ZX&pid=64225&retries=0&service.filter=echo&side=provider×tamp=1562076628&version=&warmup=100") assert.NoError(t, err) - configurator.Configure(&providerUrl) + configurator.Configure(providerUrl) assert.Equal(t, failfast, providerUrl.GetParam(constant.CLUSTER_KEY, "")) } diff --git a/config_center/dynamic_configuration.go b/config_center/dynamic_configuration.go index 540febc9d38e164afcc62538478df140b7d671c7..69f84213e7ecd226331821a07fc9bcfc48cd735c 100644 --- a/config_center/dynamic_configuration.go +++ b/config_center/dynamic_configuration.go @@ -58,6 +58,9 @@ type DynamicConfiguration interface { // PublishConfig will publish the config with the (key, group, value) pair PublishConfig(string, string, string) error + // RemoveConfig will remove the config white the (key, group) pair + RemoveConfig(string, string) error + // GetConfigKeysByGroup will return all keys with the group GetConfigKeysByGroup(group string) (*gxset.HashSet, error) } @@ -86,6 +89,6 @@ func WithTimeout(time time.Duration) Option { } // GetRuleKey The format is '{interfaceName}:[version]:[group]' -func GetRuleKey(url common.URL) string { +func GetRuleKey(url *common.URL) string { return url.ColonSeparatedKey() } diff --git a/config_center/file/factory.go b/config_center/file/factory.go new file mode 100644 index 0000000000000000000000000000000000000000..2dda900e20cb7476b1d8da95e4b2b26fcb9dcefd --- /dev/null +++ b/config_center/file/factory.go @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package file + +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/config_center" + "github.com/apache/dubbo-go/config_center/parser" +) + +func init() { + extension.SetConfigCenterFactory(constant.FILE_KEY, func() config_center.DynamicConfigurationFactory { + return &fileDynamicConfigurationFactory{} + }) +} + +type fileDynamicConfigurationFactory struct { +} + +// GetDynamicConfiguration Get Configuration with URL +func (f *fileDynamicConfigurationFactory) GetDynamicConfiguration(url *common.URL) (config_center.DynamicConfiguration, + error) { + dynamicConfiguration, err := newFileSystemDynamicConfiguration(url) + if err != nil { + return nil, perrors.WithStack(err) + } + + dynamicConfiguration.SetParser(&parser.DefaultConfigurationParser{}) + return dynamicConfiguration, err +} diff --git a/config_center/file/impl.go b/config_center/file/impl.go new file mode 100644 index 0000000000000000000000000000000000000000..f29a33d5e2519b54a5496f5ea03ff170020bb28c --- /dev/null +++ b/config_center/file/impl.go @@ -0,0 +1,309 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package file + +import ( + "bytes" + "errors" + "io/ioutil" + "os" + "os/exec" + "os/user" + "path" + "path/filepath" + "runtime" + "strings" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/config_center/parser" +) + +const ( + PARAM_NAME_PREFIX = "dubbo.config-center." + CONFIG_CENTER_DIR_PARAM_NAME = PARAM_NAME_PREFIX + "dir" + CONFIG_CENTER_ENCODING_PARAM_NAME = PARAM_NAME_PREFIX + "encoding" + DEFAULT_CONFIG_CENTER_ENCODING = "UTF-8" +) + +// FileSystemDynamicConfiguration +type FileSystemDynamicConfiguration struct { + config_center.BaseDynamicConfiguration + url *common.URL + rootPath string + encoding string + cacheListener *CacheListener + parser parser.ConfigurationParser +} + +func newFileSystemDynamicConfiguration(url *common.URL) (*FileSystemDynamicConfiguration, error) { + encode := url.GetParam(CONFIG_CENTER_ENCODING_PARAM_NAME, DEFAULT_CONFIG_CENTER_ENCODING) + + root := url.GetParam(CONFIG_CENTER_DIR_PARAM_NAME, "") + var c *FileSystemDynamicConfiguration + if _, err := os.Stat(root); err != nil { + // not exist, use default, /XXX/xx/.dubbo/config-center + if rp, err := Home(); err != nil { + return nil, perrors.WithStack(err) + } else { + root = path.Join(rp, ".dubbo", "config-center") + } + } + + if _, err := os.Stat(root); err != nil { + // it must be dir, if not exist, will create + if err = createDir(root); err != nil { + return nil, perrors.WithStack(err) + } + } + + c = &FileSystemDynamicConfiguration{ + url: url, + rootPath: root, + encoding: encode, + } + + c.cacheListener = NewCacheListener(c.rootPath) + + return c, nil +} + +// RootPath get root path +func (fsdc *FileSystemDynamicConfiguration) RootPath() string { + return fsdc.rootPath +} + +// Parser Get Parser +func (fsdc *FileSystemDynamicConfiguration) Parser() parser.ConfigurationParser { + return fsdc.parser +} + +// SetParser Set Parser +func (fsdc *FileSystemDynamicConfiguration) SetParser(p parser.ConfigurationParser) { + fsdc.parser = p +} + +// AddListener Add listener +func (fsdc *FileSystemDynamicConfiguration) AddListener(key string, listener config_center.ConfigurationListener, + opts ...config_center.Option) { + tmpOpts := &config_center.Options{} + for _, opt := range opts { + opt(tmpOpts) + } + + tmpPath := fsdc.GetPath(key, tmpOpts.Group) + fsdc.cacheListener.AddListener(tmpPath, listener) +} + +// RemoveListener Remove listener +func (fsdc *FileSystemDynamicConfiguration) RemoveListener(key string, listener config_center.ConfigurationListener, + opts ...config_center.Option) { + tmpOpts := &config_center.Options{} + for _, opt := range opts { + opt(tmpOpts) + } + + tmpPath := fsdc.GetPath(key, tmpOpts.Group) + fsdc.cacheListener.RemoveListener(tmpPath, listener) +} + +// GetProperties get properties file +func (fsdc *FileSystemDynamicConfiguration) GetProperties(key string, opts ...config_center.Option) (string, error) { + tmpOpts := &config_center.Options{} + for _, opt := range opts { + opt(tmpOpts) + } + + tmpPath := fsdc.GetPath(key, tmpOpts.Group) + file, err := ioutil.ReadFile(tmpPath) + if err != nil { + return "", perrors.WithStack(err) + } + return string(file), nil +} + +// GetRule get Router rule properties file +func (fsdc *FileSystemDynamicConfiguration) GetRule(key string, opts ...config_center.Option) (string, error) { + return fsdc.GetProperties(key, opts...) +} + +// GetInternalProperty get value by key in Default properties file(dubbo.properties) +func (fsdc *FileSystemDynamicConfiguration) GetInternalProperty(key string, opts ...config_center.Option) (string, + error) { + return fsdc.GetProperties(key, opts...) +} + +// PublishConfig will publish the config with the (key, group, value) pair +func (fsdc *FileSystemDynamicConfiguration) PublishConfig(key string, group string, value string) error { + tmpPath := fsdc.GetPath(key, group) + return fsdc.write2File(tmpPath, value) +} + +// GetConfigKeysByGroup will return all keys with the group +func (fsdc *FileSystemDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.HashSet, error) { + tmpPath := fsdc.GetPath("", group) + r := gxset.NewSet() + + fileInfo, _ := ioutil.ReadDir(tmpPath) + + for _, file := range fileInfo { + // list file + if file.IsDir() { + continue + } + + r.Add(file.Name()) + } + + return r, nil +} + +// RemoveConfig will remove the config whit hte (key, group) +func (fsdc *FileSystemDynamicConfiguration) RemoveConfig(key string, group string) error { + tmpPath := fsdc.GetPath(key, group) + _, err := fsdc.deleteDelay(tmpPath) + return err +} + +// Close close file watcher +func (fsdc *FileSystemDynamicConfiguration) Close() error { + return fsdc.cacheListener.Close() +} + +// GetPath get path +func (fsdc *FileSystemDynamicConfiguration) GetPath(key string, group string) string { + if len(key) == 0 { + return path.Join(fsdc.rootPath, group) + } + + if len(group) == 0 { + group = config_center.DEFAULT_GROUP + } + + return path.Join(fsdc.rootPath, group, key) +} + +func (fsdc *FileSystemDynamicConfiguration) deleteDelay(path string) (bool, error) { + if path == "" { + return false, nil + } + + if err := os.RemoveAll(path); err != nil { + return false, err + } + + return true, nil +} + +func (fsdc *FileSystemDynamicConfiguration) write2File(fp string, value string) error { + if err := forceMkdirParent(fp); err != nil { + return perrors.WithStack(err) + } + + return ioutil.WriteFile(fp, []byte(value), os.ModePerm) +} + +func forceMkdirParent(fp string) error { + pd := getParentDirectory(fp) + + return createDir(pd) +} + +func createDir(path string) error { + // create dir, chmod is drwxrwxrwx(0777) + if err := os.MkdirAll(path, os.ModePerm); err != nil { + return err + } + + return nil +} + +func getParentDirectory(fp string) string { + return substr(fp, 0, strings.LastIndex(fp, string(filepath.Separator))) +} + +func substr(s string, pos, length int) string { + runes := []rune(s) + l := pos + length + if l > len(runes) { + l = len(runes) + } + return string(runes[pos:l]) +} + +// Home returns the home directory for the executing user. +// +// This uses an OS-specific method for discovering the home directory. +// An error is returned if a home directory cannot be detected. +func Home() (string, error) { + currentUser, err := user.Current() + if nil == err { + return currentUser.HomeDir, nil + } + + // cross compile support + if "windows" == runtime.GOOS { + return homeWindows() + } + + // Unix-like system, so just assume Unix + return homeUnix() +} + +func homeUnix() (string, error) { + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); home != "" { + return home, nil + } + + // If that fails, try the shell + var stdout bytes.Buffer + cmd := exec.Command("sh", "-c", "eval echo ~$USER") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + result := strings.TrimSpace(stdout.String()) + if result == "" { + return "", errors.New("blank output when reading home directory") + } + + return result, nil +} + +func homeWindows() (string, error) { + drive := os.Getenv("HOMEDRIVE") + homePath := os.Getenv("HOMEPATH") + home := drive + homePath + if drive == "" || homePath == "" { + home = os.Getenv("USERPROFILE") + } + if home == "" { + return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") + } + + return home, nil +} diff --git a/config_center/file/impl_test.go b/config_center/file/impl_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e912cb7078fe42b514ba510db6b7f871ab998ea9 --- /dev/null +++ b/config_center/file/impl_test.go @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package file + +import ( + "fmt" + "os" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config_center" +) + +const ( + key = "com.dubbo.go" +) + +func initFileData(t *testing.T) (*FileSystemDynamicConfiguration, error) { + urlString := "registry://127.0.0.1:2181" + regurl, err := common.NewURL(urlString) + assert.NoError(t, err) + dc, err := extension.GetConfigCenterFactory("file").GetDynamicConfiguration(regurl) + assert.NoError(t, err) + + return dc.(*FileSystemDynamicConfiguration), err +} + +func TestPublishAndGetConfig(t *testing.T) { + file, err := initFileData(t) + assert.NoError(t, err) + err = file.PublishConfig(key, "", "A") + assert.NoError(t, err) + + prop, err := file.GetProperties(key) + assert.NoError(t, err) + assert.Equal(t, "A", prop) + + defer destroy(file.rootPath, file) +} + +func TestAddListener(t *testing.T) { + file, err := initFileData(t) + group := "dubbogo" + value := "Test Value" + err = file.PublishConfig(key, group, value) + assert.NoError(t, err) + + listener := &mockDataListener{} + file.AddListener(key, listener, config_center.WithGroup(group)) + + value = "Test Value 2" + err = file.PublishConfig(key, group, value) + assert.NoError(t, err) + // remove need wait a moment + time.Sleep(time.Second) + defer destroy(file.rootPath, file) +} + +func TestRemoveListener(t *testing.T) { + file, err := initFileData(t) + group := "dubbogo" + value := "Test Value" + err = file.PublishConfig(key, group, value) + assert.NoError(t, err) + + listener := &mockDataListener{} + file.AddListener(key, listener, config_center.WithGroup(group)) + + value = "Test Value 2" + err = file.PublishConfig(key, group, value) + assert.NoError(t, err) + + // make sure callback before RemoveListener + time.Sleep(time.Second) + file.RemoveListener(key, listener, config_center.WithGroup(group)) + value = "Test Value 3" + err = file.PublishConfig(key, group, value) + assert.NoError(t, err) + // remove need wait a moment + time.Sleep(time.Second) + defer destroy(file.rootPath, file) +} + +func TestGetConfigKeysByGroup(t *testing.T) { + file, err := initFileData(t) + group := "dubbogo" + value := "Test Value" + err = file.PublishConfig(key, group, value) + gs, err := file.GetConfigKeysByGroup(group) + assert.NoError(t, err) + assert.Equal(t, 1, gs.Size()) + assert.Equal(t, key, gs.Values()[0]) + // remove need wait a moment + time.Sleep(time.Second) + defer destroy(file.rootPath, file) +} + +func TestGetConfig(t *testing.T) { + file, err := initFileData(t) + assert.NoError(t, err) + group := "dubbogo" + value := "Test Value" + err = file.PublishConfig(key, group, value) + assert.NoError(t, err) + prop, err := file.GetProperties(key, config_center.WithGroup(group)) + assert.NoError(t, err) + assert.Equal(t, value, prop) + defer destroy(file.rootPath, file) +} + +func TestPublishConfig(t *testing.T) { + file, err := initFileData(t) + assert.NoError(t, err) + group := "dubbogo" + value := "Test Value" + err = file.PublishConfig(key, group, value) + assert.NoError(t, err) + prop, err := file.GetInternalProperty(key, config_center.WithGroup(group)) + assert.NoError(t, err) + assert.Equal(t, value, prop) + defer destroy(file.rootPath, file) +} + +func destroy(path string, fdc *FileSystemDynamicConfiguration) { + fdc.Close() + os.RemoveAll(path) +} + +type mockDataListener struct{} + +func (l *mockDataListener) Process(configType *config_center.ConfigChangeEvent) { + fmt.Printf("process!!!!! %v", configType) +} diff --git a/config_center/file/listener.go b/config_center/file/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..d569030e5ac6a127a862c4d22d180f674cadce2d --- /dev/null +++ b/config_center/file/listener.go @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package file + +import ( + "io/ioutil" + "sync" +) + +import ( + "github.com/fsnotify/fsnotify" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/remoting" +) + +// CacheListener is file watcher +type CacheListener struct { + watch *fsnotify.Watcher + keyListeners sync.Map + rootPath string +} + +// NewCacheListener creates a new CacheListener +func NewCacheListener(rootPath string) *CacheListener { + cl := &CacheListener{rootPath: rootPath} + // start watcher + watch, err := fsnotify.NewWatcher() + if err != nil { + logger.Errorf("file : listen config fail, error:%v ", err) + } + go func() { + for { + select { + case event := <-watch.Events: + key := event.Name + logger.Debugf("watcher %s, event %v", cl.rootPath, event) + if event.Op&fsnotify.Write == fsnotify.Write { + if l, ok := cl.keyListeners.Load(key); ok { + dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, + remoting.EventTypeUpdate) + } + } + if event.Op&fsnotify.Create == fsnotify.Create { + if l, ok := cl.keyListeners.Load(key); ok { + dataChangeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, + remoting.EventTypeAdd) + } + } + if event.Op&fsnotify.Remove == fsnotify.Remove { + if l, ok := cl.keyListeners.Load(key); ok { + removeCallback(l.(map[config_center.ConfigurationListener]struct{}), key, remoting.EventTypeDel) + } + } + case err := <-watch.Errors: + // err may be nil, ignore + if err != nil { + logger.Warnf("file : listen watch fail:%+v", err) + } + } + } + }() + cl.watch = watch + + extension.AddCustomShutdownCallback(func() { + cl.watch.Close() + }) + + return cl +} + +func removeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) { + if len(lmap) == 0 { + logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event) + return + } + for l := range lmap { + callback(l, key, "", event) + } +} + +func dataChangeCallback(lmap map[config_center.ConfigurationListener]struct{}, key string, event remoting.EventType) { + if len(lmap) == 0 { + logger.Warnf("file watch callback but configuration listener is empty, key:%s, event:%v", key, event) + return + } + c := getFileContent(key) + for l := range lmap { + callback(l, key, c, event) + } +} + +func callback(listener config_center.ConfigurationListener, path, data string, event remoting.EventType) { + listener.Process(&config_center.ConfigChangeEvent{Key: path, Value: data, ConfigType: event}) +} + +// Close will remove key listener and close watcher +func (cl *CacheListener) Close() error { + cl.keyListeners.Range(func(key, value interface{}) bool { + cl.keyListeners.Delete(key) + return true + }) + return cl.watch.Close() +} + +// AddListener will add a listener if loaded +// if you watcher a file or directory not exist, will error with no such file or directory +func (cl *CacheListener) AddListener(key string, listener config_center.ConfigurationListener) { + // reference from https://stackoverflow.com/questions/34018908/golang-why-dont-we-have-a-set-datastructure + // make a map[your type]struct{} like set in java + listeners, loaded := cl.keyListeners.LoadOrStore(key, map[config_center.ConfigurationListener]struct{}{ + listener: {}}) + if loaded { + listeners.(map[config_center.ConfigurationListener]struct{})[listener] = struct{}{} + cl.keyListeners.Store(key, listeners) + return + } + if err := cl.watch.Add(key); err != nil { + logger.Errorf("watcher add path:%s err:%v", key, err) + } +} + +// RemoveListener will delete a listener if loaded +func (cl *CacheListener) RemoveListener(key string, listener config_center.ConfigurationListener) { + listeners, loaded := cl.keyListeners.Load(key) + if !loaded { + return + } + delete(listeners.(map[config_center.ConfigurationListener]struct{}), listener) + if err := cl.watch.Remove(key); err != nil { + logger.Errorf("watcher remove path:%s err:%v", key, err) + } +} + +func getFileContent(path string) string { + c, err := ioutil.ReadFile(path) + if err != nil { + logger.Errorf("read file path:%s err:%v", path, err) + return "" + } + + return string(c) +} diff --git a/config_center/mock_dynamic_config.go b/config_center/mock_dynamic_config.go index 8fe0a251239f7bfc6a3f70c3834da1b3af8484ba..9bebd600c6ba9e09f172f9260a920b6572fa694c 100644 --- a/config_center/mock_dynamic_config.go +++ b/config_center/mock_dynamic_config.go @@ -98,6 +98,7 @@ func (c *MockDynamicConfiguration) GetConfigKeysByGroup(group string) (*gxset.Ha // MockDynamicConfiguration uses to parse content and defines listener type MockDynamicConfiguration struct { + BaseDynamicConfiguration parser parser.ConfigurationParser content string listener map[string]ConfigurationListener diff --git a/config_center/nacos/client.go b/config_center/nacos/client.go index acfe2609ceb0fc62650b7b494b25084ea4df6946..f3942c023d6bc7b7c7afe09863283cbc315fdb1f 100644 --- a/config_center/nacos/client.go +++ b/config_center/nacos/client.go @@ -122,7 +122,7 @@ func ValidateNacosClient(container nacosClientFacade, opts ...option) error { return perrors.WithMessagef(nil, "newNacosClient(address:%+v)", url.PrimitiveURL) } -func newNacosClient(name string, nacosAddrs []string, timeout time.Duration, url common.URL) (*NacosClient, error) { +func newNacosClient(name string, nacosAddrs []string, timeout time.Duration, url *common.URL) (*NacosClient, error) { var ( err error n *NacosClient @@ -149,8 +149,8 @@ func newNacosClient(name string, nacosAddrs []string, timeout time.Duration, url return n, nil } -func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url common.URL) (config_client.IConfigClient, error) { - svrConfList := []nacosconst.ServerConfig{} +func initNacosConfigClient(nacosAddrs []string, timeout time.Duration, url *common.URL) (config_client.IConfigClient, error) { + var svrConfList []nacosconst.ServerConfig for _, nacosAddr := range nacosAddrs { split := strings.Split(nacosAddr, ":") port, err := strconv.ParseUint(split[1], 10, 64) diff --git a/config_center/nacos/client_test.go b/config_center/nacos/client_test.go index 01319f362b956a0a15be8d5d4dde2a8b2be57c89..2ce3e375557c69721611799d414b3db31d141fca 100644 --- a/config_center/nacos/client_test.go +++ b/config_center/nacos/client_test.go @@ -36,7 +36,7 @@ func TestNewNacosClient(t *testing.T) { nacosURL := strings.ReplaceAll(server.URL, "http", "registry") registryUrl, _ := common.NewURL(nacosURL) c := &nacosDynamicConfiguration{ - url: ®istryUrl, + url: registryUrl, done: make(chan struct{}), } err := ValidateNacosClient(c, WithNacosName(nacosClientName)) @@ -59,7 +59,7 @@ func TestSetNacosClient(t *testing.T) { nacosURL := "registry://" + server.Listener.Addr().String() registryUrl, _ := common.NewURL(nacosURL) c := &nacosDynamicConfiguration{ - url: ®istryUrl, + url: registryUrl, done: make(chan struct{}), } var client *NacosClient @@ -93,7 +93,7 @@ func TestNewNacosClient_connectError(t *testing.T) { registryUrl, err := common.NewURL(nacosURL) assert.NoError(t, err) c := &nacosDynamicConfiguration{ - url: ®istryUrl, + url: registryUrl, done: make(chan struct{}), } err = ValidateNacosClient(c, WithNacosName(nacosClientName)) diff --git a/config_center/nacos/impl.go b/config_center/nacos/impl.go index bbf707b93811663d0a259c6704e1008bfa91c5c1..7c67930026369e7e2b6c58fc57d05d3a9edb50cc 100644 --- a/config_center/nacos/impl.go +++ b/config_center/nacos/impl.go @@ -47,6 +47,7 @@ const ( // nacosDynamicConfiguration is the implementation of DynamicConfiguration based on nacos type nacosDynamicConfiguration struct { + config_center.BaseDynamicConfiguration url *common.URL rootPath string wg sync.WaitGroup @@ -185,8 +186,8 @@ func (n *nacosDynamicConfiguration) GetDone() chan struct{} { } // GetUrl Get Url -func (n *nacosDynamicConfiguration) GetUrl() common.URL { - return *n.url +func (n *nacosDynamicConfiguration) GetUrl() *common.URL { + return n.url } // Destroy Destroy configuration instance diff --git a/config_center/nacos/impl_test.go b/config_center/nacos/impl_test.go index 88d200edc927c62967975dff28e19c03743125f0..40efa5f4f288d0eff6d9648481cb2a14bfdb3931 100644 --- a/config_center/nacos/impl_test.go +++ b/config_center/nacos/impl_test.go @@ -73,7 +73,7 @@ func initNacosData(t *testing.T) (*nacosDynamicConfiguration, error) { nacosURL := strings.ReplaceAll(server.URL, "http", "registry") regurl, _ := common.NewURL(nacosURL) factory := &nacosDynamicConfigurationFactory{} - nacosConfiguration, err := factory.GetDynamicConfiguration(®url) + nacosConfiguration, err := factory.GetDynamicConfiguration(regurl) assert.NoError(t, err) nacosConfiguration.SetParser(&parser.DefaultConfigurationParser{}) @@ -105,7 +105,7 @@ func TestNacosDynamicConfiguration_GetConfigKeysByGroup(t *testing.T) { nacosURL := strings.ReplaceAll(ts.URL, "http", "registry") regurl, _ := common.NewURL(nacosURL) - nacosConfiguration, err := newNacosDynamicConfiguration(®url) + nacosConfiguration, err := newNacosDynamicConfiguration(regurl) assert.NoError(t, err) nacosConfiguration.SetParser(&parser.DefaultConfigurationParser{}) diff --git a/config_center/parser/configuration_parser.go b/config_center/parser/configuration_parser.go index f794221f9cf3c689d52b32a57ab51f7908f17297..b104d3ddb6b2e10842b82a0498e039cb7e17ab12 100644 --- a/config_center/parser/configuration_parser.go +++ b/config_center/parser/configuration_parser.go @@ -144,14 +144,14 @@ func serviceItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.UR if err != nil { return nil, perrors.WithStack(err) } - urls = append(urls, &url) + urls = append(urls, url) } } else { url, err := common.NewURL(urlStr) if err != nil { return nil, perrors.WithStack(err) } - urls = append(urls, &url) + urls = append(urls, url) } } return urls, nil @@ -192,7 +192,7 @@ func appItemToUrls(item ConfigItem, config ConfiguratorConfig) ([]*common.URL, e if err != nil { return nil, perrors.WithStack(err) } - urls = append(urls, &url) + urls = append(urls, url) } } return urls, nil diff --git a/config_center/zookeeper/impl.go b/config_center/zookeeper/impl.go index ef579eb2d11cf5f5bafb132c3e201c12ee7845c0..17812e917f533d28c052c7cda3411de44b9aa7b5 100644 --- a/config_center/zookeeper/impl.go +++ b/config_center/zookeeper/impl.go @@ -44,6 +44,7 @@ const ( ) type zookeeperDynamicConfiguration struct { + config_center.BaseDynamicConfiguration url *common.URL rootPath string wg sync.WaitGroup @@ -182,8 +183,8 @@ func (c *zookeeperDynamicConfiguration) Done() chan struct{} { return c.done } -func (c *zookeeperDynamicConfiguration) GetUrl() common.URL { - return *c.url +func (c *zookeeperDynamicConfiguration) GetUrl() *common.URL { + return c.url } func (c *zookeeperDynamicConfiguration) Destroy() { diff --git a/config_center/zookeeper/impl_test.go b/config_center/zookeeper/impl_test.go index ecc3527c486fdd2aa2bc6f4d2f2adab1147e495a..d4a9de41fd62d48ce4528056289839a38285c9a3 100644 --- a/config_center/zookeeper/impl_test.go +++ b/config_center/zookeeper/impl_test.go @@ -50,7 +50,7 @@ func initZkData(group string, t *testing.T) (*zk.TestCluster, *zookeeperDynamicC assert.NoError(t, err) regurl.AddParam(constant.REGISTRY_TIMEOUT_KEY, "15s") zkFactory := &zookeeperDynamicConfigurationFactory{} - reg, err := zkFactory.GetDynamicConfiguration(®url) + reg, err := zkFactory.GetDynamicConfiguration(regurl) zreg, ok := reg.(*zookeeperDynamicConfiguration) assert.True(t, ok) assert.NoError(t, err) diff --git a/contributing.md b/contributing.md index 9ee2dae32fad6caaf9e19c5e98e8b99b61c26a51..51301511e01c12e388edf574e5c8660ad4a542a1 100644 --- a/contributing.md +++ b/contributing.md @@ -38,4 +38,27 @@ The title format of the pull request `MUST` follow the following rules: ### 3.3 comment >- 1 there should be comment for every export func/var. ->- 2 the comment should begin with function name/var name. \ No newline at end of file +>- 2 the comment should begin with function name/var name. + +### 3.4 import + +We dubbogo import blocks should be splited into 3 blocks. + +```Go +// block 1: the go internal package +import ( + "fmt" +) + +// block 2: the third package +import ( + "github.com/dubbogo/xxx" + + "github.com/RoaringBitmap/roaring" +) + +// block 3: the dubbo-go package +import ( + "github.com/apache/dubbo-go/common" +) +``` \ No newline at end of file diff --git a/filter/filter.go b/filter/filter.go index 804bf3b9df8040c6377648f150c25d421b29b93d..f17c1a231e53e9a3df48072c851cbb26b42a04b4 100644 --- a/filter/filter.go +++ b/filter/filter.go @@ -20,6 +20,7 @@ package filter import ( "context" ) + import ( "github.com/apache/dubbo-go/protocol" ) diff --git a/filter/filter_impl/access_log_filter.go b/filter/filter_impl/access_log_filter.go index 621012c24c0ad12b3bda397148a3ed9c29d080ed..167b5edd804fd5b4b319e86d13815d1b4f698e1c 100644 --- a/filter/filter_impl/access_log_filter.go +++ b/filter/filter_impl/access_log_filter.go @@ -105,13 +105,31 @@ func (ef *AccessLogFilter) logIntoChannel(accessLogData AccessLogData) { func (ef *AccessLogFilter) buildAccessLogData(_ protocol.Invoker, invocation protocol.Invocation) map[string]string { dataMap := make(map[string]string, 16) attachments := invocation.Attachments() - dataMap[constant.INTERFACE_KEY] = attachments[constant.INTERFACE_KEY] - dataMap[constant.METHOD_KEY] = invocation.MethodName() - dataMap[constant.VERSION_KEY] = attachments[constant.VERSION_KEY] - dataMap[constant.GROUP_KEY] = attachments[constant.GROUP_KEY] - dataMap[constant.TIMESTAMP_KEY] = time.Now().Format(MessageDateLayout) - dataMap[constant.LOCAL_ADDR], _ = attachments[constant.LOCAL_ADDR] - dataMap[constant.REMOTE_ADDR], _ = attachments[constant.REMOTE_ADDR] + itf := attachments[constant.INTERFACE_KEY] + if itf == nil || len(itf.(string)) == 0 { + itf = attachments[constant.PATH_KEY] + } + if itf != nil { + dataMap[constant.INTERFACE_KEY] = itf.(string) + } + if v, ok := attachments[constant.METHOD_KEY]; ok && v != nil { + dataMap[constant.METHOD_KEY] = v.(string) + } + if v, ok := attachments[constant.VERSION_KEY]; ok && v != nil { + dataMap[constant.VERSION_KEY] = v.(string) + } + if v, ok := attachments[constant.GROUP_KEY]; ok && v != nil { + dataMap[constant.GROUP_KEY] = v.(string) + } + if v, ok := attachments[constant.TIMESTAMP_KEY]; ok && v != nil { + dataMap[constant.TIMESTAMP_KEY] = v.(string) + } + if v, ok := attachments[constant.LOCAL_ADDR]; ok && v != nil { + dataMap[constant.LOCAL_ADDR] = v.(string) + } + if v, ok := attachments[constant.REMOTE_ADDR]; ok && v != nil { + dataMap[constant.REMOTE_ADDR] = v.(string) + } if len(invocation.Arguments()) > 0 { builder := strings.Builder{} diff --git a/filter/filter_impl/access_log_filter_test.go b/filter/filter_impl/access_log_filter_test.go index 55c328cc30ae892c603fcc65034e48d2a52403d2..a3a6151aa1b6a933c57543248f3703125fa356d9 100644 --- a/filter/filter_impl/access_log_filter_test.go +++ b/filter/filter_impl/access_log_filter_test.go @@ -45,7 +45,7 @@ func TestAccessLogFilter_Invoke_Not_Config(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) accessLogFilter := GetAccessLogFilter() @@ -64,7 +64,7 @@ func TestAccessLogFilterInvokeDefaultConfig(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) attach[constant.VERSION_KEY] = "1.0" attach[constant.GROUP_KEY] = "MyGroup" inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) diff --git a/filter/filter_impl/active_filter_test.go b/filter/filter_impl/active_filter_test.go index 6b72830e6a1a523b775b9294863ab18f8fe518a2..9837a49c72e28c7a7209f8af6059bdc30c222cc2 100644 --- a/filter/filter_impl/active_filter_test.go +++ b/filter/filter_impl/active_filter_test.go @@ -37,7 +37,7 @@ import ( ) func TestActiveFilterInvoke(t *testing.T) { - invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, make(map[string]interface{}, 0)) url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") filter := ActiveFilter{} ctrl := gomock.NewController(t) @@ -53,7 +53,7 @@ func TestActiveFilterInvoke(t *testing.T) { func TestActiveFilterOnResponse(t *testing.T) { c := protocol.CurrentTimeMillis() elapsed := 100 - invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + invoc := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]interface{}{ dubboInvokeStartTime: strconv.FormatInt(c-int64(elapsed), 10), }) url, _ := common.NewURL("dubbo://192.168.10.10:20000/com.ikurento.user.UserProvider") diff --git a/filter/filter_impl/auth/consumer_sign.go b/filter/filter_impl/auth/consumer_sign.go index 945cf3e6e7e728042b5422174162dd5aded50361..823db82bfcb0bb165ef36bb9d141650df1e01432 100644 --- a/filter/filter_impl/auth/consumer_sign.go +++ b/filter/filter_impl/auth/consumer_sign.go @@ -42,8 +42,8 @@ func (csf *ConsumerSignFilter) Invoke(ctx context.Context, invoker protocol.Invo logger.Infof("invoking ConsumerSign filter.") url := invoker.GetUrl() - err := doAuthWork(&url, func(authenticator filter.Authenticator) error { - return authenticator.Sign(invocation, &url) + err := doAuthWork(url, func(authenticator filter.Authenticator) error { + return authenticator.Sign(invocation, url) }) if err != nil { panic(fmt.Sprintf("Sign for invocation %s # %s failed", url.ServiceKey(), invocation.MethodName())) diff --git a/filter/filter_impl/auth/default_authenticator_test.go b/filter/filter_impl/auth/default_authenticator_test.go index 5b107b5960ff5adc383d52aa5e393d9fc6e71d14..37c7e9d1837a183e72db1134506cc3f89611ac76 100644 --- a/filter/filter_impl/auth/default_authenticator_test.go +++ b/filter/filter_impl/auth/default_authenticator_test.go @@ -48,26 +48,26 @@ func TestDefaultAuthenticator_Authenticate(t *testing.T) { }{"YUYU", 1}} inv := invocation.NewRPCInvocation("test", parmas, nil) requestTime := strconv.Itoa(int(time.Now().Unix() * 1000)) - signature, _ := getSignature(&testurl, inv, secret, requestTime) + signature, _ := getSignature(testurl, inv, secret, requestTime) var authenticator = &DefaultAuthenticator{} - invcation := invocation.NewRPCInvocation("test", parmas, map[string]string{ + invcation := invocation.NewRPCInvocation("test", parmas, map[string]interface{}{ constant.REQUEST_SIGNATURE_KEY: signature, constant.CONSUMER: "test", constant.REQUEST_TIMESTAMP_KEY: requestTime, constant.AK_KEY: access, }) - err := authenticator.Authenticate(invcation, &testurl) + err := authenticator.Authenticate(invcation, testurl) assert.Nil(t, err) // modify the params - invcation = invocation.NewRPCInvocation("test", parmas[:1], map[string]string{ + invcation = invocation.NewRPCInvocation("test", parmas[:1], map[string]interface{}{ constant.REQUEST_SIGNATURE_KEY: signature, constant.CONSUMER: "test", constant.REQUEST_TIMESTAMP_KEY: requestTime, constant.AK_KEY: access, }) - err = authenticator.Authenticate(invcation, &testurl) + err = authenticator.Authenticate(invcation, testurl) assert.NotNil(t, err) } @@ -79,7 +79,7 @@ func TestDefaultAuthenticator_Sign(t *testing.T) { testurl.SetParam(constant.SECRET_ACCESS_KEY_KEY, "skey") testurl.SetParam(constant.PARAMTER_SIGNATURE_ENABLE_KEY, "false") inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, nil) - _ = authenticator.Sign(inv, &testurl) + _ = authenticator.Sign(inv, testurl) assert.NotEqual(t, inv.AttachmentsByKey(constant.REQUEST_SIGNATURE_KEY, ""), "") assert.NotEqual(t, inv.AttachmentsByKey(constant.CONSUMER, ""), "") assert.NotEqual(t, inv.AttachmentsByKey(constant.REQUEST_TIMESTAMP_KEY, ""), "") @@ -119,12 +119,12 @@ func Test_getAccessKeyPairFailed(t *testing.T) { func Test_getSignatureWithinParams(t *testing.T) { testurl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?interface=com.ikurento.user.UserProvider&group=gg&version=2.6.0") testurl.SetParam(constant.PARAMTER_SIGNATURE_ENABLE_KEY, "true") - inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]interface{}{ "": "", }) secret := "dubbo" current := strconv.Itoa(int(time.Now().Unix() * 1000)) - signature, _ := getSignature(&testurl, inv, secret, current) + signature, _ := getSignature(testurl, inv, secret, current) requestString := fmt.Sprintf(constant.SIGNATURE_STRING_FORMAT, testurl.ColonSeparatedKey(), inv.MethodName(), secret, current) s, _ := SignWithParams(inv.Arguments(), requestString, secret) @@ -138,7 +138,7 @@ func Test_getSignature(t *testing.T) { inv := invocation.NewRPCInvocation("test", []interface{}{"OK"}, nil) secret := "dubbo" current := strconv.Itoa(int(time.Now().Unix() * 1000)) - signature, _ := getSignature(&testurl, inv, secret, current) + signature, _ := getSignature(testurl, inv, secret, current) requestString := fmt.Sprintf(constant.SIGNATURE_STRING_FORMAT, testurl.ColonSeparatedKey(), inv.MethodName(), secret, current) s := Sign(requestString, secret) diff --git a/filter/filter_impl/auth/provider_auth.go b/filter/filter_impl/auth/provider_auth.go index d5f5db300d4e7c94978d5d52e32f741f7d27bb48..774fdb2fb8f69bc33fcbee36faae28f8f6e6d0cc 100644 --- a/filter/filter_impl/auth/provider_auth.go +++ b/filter/filter_impl/auth/provider_auth.go @@ -42,8 +42,8 @@ func (paf *ProviderAuthFilter) Invoke(ctx context.Context, invoker protocol.Invo logger.Infof("invoking providerAuth filter.") url := invoker.GetUrl() - err := doAuthWork(&url, func(authenticator filter.Authenticator) error { - return authenticator.Authenticate(invocation, &url) + err := doAuthWork(url, func(authenticator filter.Authenticator) error { + return authenticator.Authenticate(invocation, url) }) if err != nil { logger.Infof("auth the request: %v occur exception, cause: %s", invocation, err.Error()) diff --git a/filter/filter_impl/auth/provider_auth_test.go b/filter/filter_impl/auth/provider_auth_test.go index 626782ae8390f046f441c1f162700a883e6f22d0..dc130b59851ddb02572103b2e29361f9fc8f1c59 100644 --- a/filter/filter_impl/auth/provider_auth_test.go +++ b/filter/filter_impl/auth/provider_auth_test.go @@ -52,9 +52,9 @@ func TestProviderAuthFilter_Invoke(t *testing.T) { } inv := invocation.NewRPCInvocation("test", parmas, nil) requestTime := strconv.Itoa(int(time.Now().Unix() * 1000)) - signature, _ := getSignature(&url, inv, secret, requestTime) + signature, _ := getSignature(url, inv, secret, requestTime) - inv = invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]string{ + inv = invocation.NewRPCInvocation("test", []interface{}{"OK"}, map[string]interface{}{ constant.REQUEST_SIGNATURE_KEY: signature, constant.CONSUMER: "test", constant.REQUEST_TIMESTAMP_KEY: requestTime, diff --git a/filter/filter_impl/echo_filter_test.go b/filter/filter_impl/echo_filter_test.go index b821a1a30cb8af7e957fac45beecd46067627f91..f2ec81a3e63a81c66ef08d482f111e8bae5774b8 100644 --- a/filter/filter_impl/echo_filter_test.go +++ b/filter/filter_impl/echo_filter_test.go @@ -34,10 +34,10 @@ import ( func TestEchoFilterInvoke(t *testing.T) { filter := GetFilter() - result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(common.URL{}), invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, nil)) + result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(&common.URL{}), invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, nil)) assert.Equal(t, "OK", result.Result()) - result = filter.Invoke(context.Background(), protocol.NewBaseInvoker(common.URL{}), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, nil)) + result = filter.Invoke(context.Background(), protocol.NewBaseInvoker(&common.URL{}), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, nil)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } diff --git a/filter/filter_impl/execute_limit_filter_test.go b/filter/filter_impl/execute_limit_filter_test.go index d36d6edef1ec52c24a9ccd64233b4620b4f10bc7..2aebcaa8fa62278da3092d6359d2a01571be37d0 100644 --- a/filter/filter_impl/execute_limit_filter_test.go +++ b/filter/filter_impl/execute_limit_filter_test.go @@ -36,7 +36,7 @@ import ( func TestExecuteLimitFilterInvokeIgnored(t *testing.T) { methodName := "hello" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), @@ -44,14 +44,14 @@ func TestExecuteLimitFilterInvokeIgnored(t *testing.T) { limitFilter := GetExecuteLimitFilter() - result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) + result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(invokeUrl), invoc) assert.NotNil(t, result) assert.Nil(t, result.Error()) } func TestExecuteLimitFilterInvokeConfigureError(t *testing.T) { methodName := "hello1" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), @@ -61,14 +61,14 @@ func TestExecuteLimitFilterInvokeConfigureError(t *testing.T) { limitFilter := GetExecuteLimitFilter() - result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) + result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(invokeUrl), invoc) assert.NotNil(t, result) assert.Nil(t, result.Error()) } func TestExecuteLimitFilterInvoke(t *testing.T) { methodName := "hello1" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), @@ -78,7 +78,7 @@ func TestExecuteLimitFilterInvoke(t *testing.T) { limitFilter := GetExecuteLimitFilter() - result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) + result := limitFilter.Invoke(context.Background(), protocol.NewBaseInvoker(invokeUrl), invoc) assert.NotNil(t, result) assert.Nil(t, result.Error()) } diff --git a/filter/filter_impl/generic_filter.go b/filter/filter_impl/generic_filter.go index d385054ed98518177aea5e574e034cb35c72e398..36b4b13186361d85beea0625eeda1ad58c3f2dca 100644 --- a/filter/filter_impl/generic_filter.go +++ b/filter/filter_impl/generic_filter.go @@ -21,6 +21,7 @@ import ( "context" "reflect" "strings" + "time" ) import ( @@ -93,13 +94,21 @@ func struct2MapAll(obj interface{}) interface{} { if t.Kind() == reflect.Struct { result := make(map[string]interface{}, t.NumField()) for i := 0; i < t.NumField(); i++ { - if v.Field(i).Kind() == reflect.Struct || v.Field(i).Kind() == reflect.Slice || v.Field(i).Kind() == reflect.Map { - if v.Field(i).CanInterface() { - setInMap(result, t.Field(i), struct2MapAll(v.Field(i).Interface())) + field := t.Field(i) + value := v.Field(i) + kind := value.Kind() + if kind == reflect.Struct || kind == reflect.Slice || kind == reflect.Map { + if value.CanInterface() { + tmp := value.Interface() + if _, ok := tmp.(time.Time); ok { + setInMap(result, field, tmp) + continue + } + setInMap(result, field, struct2MapAll(tmp)) } } else { - if v.Field(i).CanInterface() { - setInMap(result, t.Field(i), v.Field(i).Interface()) + if value.CanInterface() { + setInMap(result, field, value.Interface()) } } } diff --git a/filter/filter_impl/generic_filter_test.go b/filter/filter_impl/generic_filter_test.go index e40733209b2e1db972ab576dea54f206a1e888c0..40cf743106821f12425d1abe6dbc82bad0559917 100644 --- a/filter/filter_impl/generic_filter_test.go +++ b/filter/filter_impl/generic_filter_test.go @@ -20,6 +20,7 @@ package filter_impl import ( "reflect" "testing" + "time" ) import ( @@ -38,6 +39,8 @@ func TestStruct2MapAll(t *testing.T) { Xx string `m:"xx"` } `m:"xxYy"` } `m:"caCa"` + DaDa time.Time + EeEe int } testData.AaAa = "1" testData.BaBa = "1" @@ -45,6 +48,8 @@ func TestStruct2MapAll(t *testing.T) { testData.CaCa.AaAa = "2" testData.CaCa.XxYy.xxXx = "3" testData.CaCa.XxYy.Xx = "3" + testData.DaDa = time.Date(2020, 10, 29, 2, 34, 0, 0, time.Local) + testData.EeEe = 100 m := struct2MapAll(testData).(map[string]interface{}) assert.Equal(t, "1", m["aaAa"].(string)) assert.Equal(t, "1", m["baBa"].(string)) @@ -53,6 +58,8 @@ func TestStruct2MapAll(t *testing.T) { assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"]).Kind()) assert.Equal(t, reflect.Map, reflect.TypeOf(m["caCa"].(map[string]interface{})["xxYy"]).Kind()) + assert.Equal(t, "2020-10-29 02:34:00", m["daDa"].(time.Time).Format("2006-01-02 15:04:05")) + assert.Equal(t, 100, m["eeEe"].(int)) } type testStruct struct { diff --git a/filter/filter_impl/generic_service_filter.go b/filter/filter_impl/generic_service_filter.go index 3711e68cce67dacfec074a5e44c080a629bce305..89b009b8d136d34ca0fd14548b3ef5536c7234d8 100644 --- a/filter/filter_impl/generic_service_filter.go +++ b/filter/filter_impl/generic_service_filter.go @@ -20,7 +20,6 @@ package filter_impl import ( "context" "reflect" - "strings" ) import ( @@ -75,7 +74,7 @@ func (ef *GenericServiceFilter) Invoke(ctx context.Context, invoker protocol.Inv url := invoker.GetUrl() methodName = invocation.Arguments()[0].(string) // get service - svc := common.ServiceMap.GetService(url.Protocol, strings.TrimPrefix(url.Path, "/")) + svc := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey()) // get method method := svc.Method()[methodName] if method == nil { diff --git a/filter/filter_impl/generic_service_filter_test.go b/filter/filter_impl/generic_service_filter_test.go index 67819717cf07383373f905ed9420a42cc0bfddb9..8b2fb549c299bb6c1cf65c4254ba6784caec5655 100644 --- a/filter/filter_impl/generic_service_filter_test.go +++ b/filter/filter_impl/generic_service_filter_test.go @@ -96,7 +96,7 @@ func TestGenericServiceFilterInvoke(t *testing.T) { hessian.Object("222")}, } s := &TestService{} - _, _ = common.ServiceMap.Register("TestService", "testprotocol", s) + _, _ = common.ServiceMap.Register("com.test.Path", "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_test.go b/filter/filter_impl/graceful_shutdown_filter_test.go index 87ac2eac616a20617b7a5e68254a1f47ecb8ac17..447a557ad5f508bf444ccae5dacb8a8559c933ea 100644 --- a/filter/filter_impl/graceful_shutdown_filter_test.go +++ b/filter/filter_impl/graceful_shutdown_filter_test.go @@ -39,7 +39,7 @@ import ( ) func TestGenericFilterInvoke(t *testing.T) { - invoc := invocation.NewRPCInvocation("GetUser", []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation("GetUser", []interface{}{"OK"}, make(map[string]interface{}, 0)) invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{})) @@ -54,7 +54,7 @@ func TestGenericFilterInvoke(t *testing.T) { assert.Equal(t, extension.GetRejectedExecutionHandler(constant.DEFAULT_KEY), shutdownFilter.getRejectHandler()) - result := shutdownFilter.Invoke(context.Background(), protocol.NewBaseInvoker(*invokeUrl), invoc) + result := shutdownFilter.Invoke(context.Background(), protocol.NewBaseInvoker(invokeUrl), invoc) assert.NotNil(t, result) assert.Nil(t, result.Error()) @@ -65,7 +65,7 @@ func TestGenericFilterInvoke(t *testing.T) { shutdownFilter.shutdownConfig = providerConfig.ShutdownConfig assert.True(t, shutdownFilter.rejectNewRequest()) - result = shutdownFilter.OnResponse(nil, nil, protocol.NewBaseInvoker(*invokeUrl), invoc) + result = shutdownFilter.OnResponse(nil, nil, protocol.NewBaseInvoker(invokeUrl), invoc) rejectHandler := &common2.OnlyLogRejectedExecutionHandler{} extension.SetRejectedExecutionHandler("mock", func() filter.RejectedExecutionHandler { diff --git a/filter/filter_impl/hystrix_filter.go b/filter/filter_impl/hystrix_filter.go index e2275149f1229c87ed4ae3f89dc9ae32d9fe6461..d13e02c06f7b2191e4d01c013a72300e1b095616 100644 --- a/filter/filter_impl/hystrix_filter.go +++ b/filter/filter_impl/hystrix_filter.go @@ -53,10 +53,6 @@ var ( providerConfigOnce sync.Once ) -//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 then the filter can be used, -//which will be done soon func init() { extension.SetFilter(HYSTRIX_CONSUMER, GetHystrixFilterConsumer) extension.SetFilter(HYSTRIX_PROVIDER, GetHystrixFilterProvider) @@ -85,7 +81,47 @@ func NewHystrixFilterError(err error, failByHystrix bool) error { } } -// nolint +/** + * HystrixFilter + * You should add hystrix related configuration in provider or consumer config or both, according to which side you are to apply HystrixFilter. + * For example: + * filter_conf: + * hystrix: + * configs: + * # =========== Define config here ============ + * "Default": + * timeout : 1000 + * max_concurrent_requests : 25 + * sleep_window : 5000 + * error_percent_threshold : 50 + * request_volume_threshold: 20 + * "userp": + * timeout: 2000 + * max_concurrent_requests: 512 + * sleep_window: 4000 + * error_percent_threshold: 35 + * request_volume_threshold: 6 + * "userp_m": + * timeout : 1200 + * max_concurrent_requests : 512 + * sleep_window : 6000 + * error_percent_threshold : 60 + * request_volume_threshold: 16 + * # =========== Define error whitelist which will be ignored by Hystrix counter ============ + * error_whitelist: [".*exception.*"] + * + * # =========== Apply default config here =========== + * default: "Default" + * + * services: + * "com.ikurento.user.UserProvider": + * # =========== Apply service level config =========== + * service_config: "userp" + * # =========== Apply method level config =========== + * methods: + * "GetUser": "userp_m" + * "GetUser1": "userp_m" + */ type HystrixFilter struct { COrP bool //true for consumer res map[string][]*regexp.Regexp @@ -213,11 +249,11 @@ func getConfig(service string, method string, cOrP bool) CommandConfigWithError func initHystrixConfigConsumer() error { if config.GetConsumerConfig().FilterConf == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_consumer") } filterConfig := config.GetConsumerConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] if filterConfig == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_consumer") } hystrixConfByte, err := yaml.Marshal(filterConfig) if err != nil { @@ -232,11 +268,11 @@ func initHystrixConfigConsumer() error { func initHystrixConfigProvider() error { if config.GetProviderConfig().FilterConf == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_provider") } - filterConfig := config.GetConsumerConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] + filterConfig := config.GetProviderConfig().FilterConf.(map[interface{}]interface{})[HYSTRIX] if filterConfig == nil { - return perrors.Errorf("no config for hystrix") + return perrors.Errorf("no config for hystrix_provider") } hystrixConfByte, err := yaml.Marshal(filterConfig) if err != nil { diff --git a/filter/filter_impl/hystrix_filter_test.go b/filter/filter_impl/hystrix_filter_test.go index eebbae55565aa7072846d368403605002df0c61b..4973ce7f70353a70befec74f1bab9333b6b58fd1 100644 --- a/filter/filter_impl/hystrix_filter_test.go +++ b/filter/filter_impl/hystrix_filter_test.go @@ -18,6 +18,7 @@ package filter_impl import ( "context" + "fmt" "regexp" "testing" ) @@ -29,6 +30,8 @@ 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" ) @@ -147,7 +150,11 @@ func (iv *testMockFailInvoker) Invoke(_ context.Context, _ protocol.Invocation) func TestHystrixFilterInvokeSuccess(t *testing.T) { hf := &HystrixFilter{} - result := hf.Invoke(context.Background(), &testMockSuccessInvoker{}, &invocation.RPCInvocation{}) + testUrl, err := common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) + assert.NoError(t, err) + testInvoker := testMockSuccessInvoker{*protocol.NewBaseInvoker(testUrl)} + result := hf.Invoke(context.Background(), &testInvoker, &invocation.RPCInvocation{}) assert.NotNil(t, result) assert.NoError(t, result.Error()) assert.NotNil(t, result.Result()) @@ -155,7 +162,11 @@ func TestHystrixFilterInvokeSuccess(t *testing.T) { func TestHystrixFilterInvokeFail(t *testing.T) { hf := &HystrixFilter{} - result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{}) + testUrl, err := common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) + assert.NoError(t, err) + testInvoker := testMockFailInvoker{*protocol.NewBaseInvoker(testUrl)} + result := hf.Invoke(context.Background(), &testInvoker, &invocation.RPCInvocation{}) assert.NotNil(t, result) assert.Error(t, result.Error()) } @@ -167,7 +178,11 @@ func TestHystricFilterInvokeCircuitBreak(t *testing.T) { resChan := make(chan protocol.Result, 50) for i := 0; i < 50; i++ { go func() { - result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{}) + testUrl, err := common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) + assert.NoError(t, err) + testInvoker := testMockSuccessInvoker{*protocol.NewBaseInvoker(testUrl)} + result := hf.Invoke(context.Background(), &testInvoker, &invocation.RPCInvocation{}) resChan <- result }() } @@ -192,7 +207,11 @@ func TestHystricFilterInvokeCircuitBreakOmitException(t *testing.T) { resChan := make(chan protocol.Result, 50) for i := 0; i < 50; i++ { go func() { - result := hf.Invoke(context.Background(), &testMockFailInvoker{}, &invocation.RPCInvocation{}) + testUrl, err := common.NewURL( + fmt.Sprintf("dubbo://%s:%d/com.ikurento.user.UserProvider", constant.LOCAL_HOST_VALUE, constant.DEFAULT_PORT)) + assert.NoError(t, err) + testInvoker := testMockSuccessInvoker{*protocol.NewBaseInvoker(testUrl)} + result := hf.Invoke(context.Background(), &testInvoker, &invocation.RPCInvocation{}) resChan <- result }() } diff --git a/filter/filter_impl/metrics_filter_test.go b/filter/filter_impl/metrics_filter_test.go index 881106f4bc5a7890569be347122da5144e440c8b..ac10d52cf3c156e3580760a4409bab49bb4d0c4f 100644 --- a/filter/filter_impl/metrics_filter_test.go +++ b/filter/filter_impl/metrics_filter_test.go @@ -57,7 +57,7 @@ func TestMetricsFilterInvoke(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) ctx := context.Background() diff --git a/filter/filter_impl/seata_filter_test.go b/filter/filter_impl/seata_filter_test.go index 6c39897f7aa3a044490e9eece8f352a9db9fc74b..45817e95cbd2eaa7365adc8a299523af8310f797 100644 --- a/filter/filter_impl/seata_filter_test.go +++ b/filter/filter_impl/seata_filter_test.go @@ -48,8 +48,9 @@ func (iv *testMockSeataInvoker) Invoke(ctx context.Context, _ protocol.Invocatio func TestSeataFilter_Invoke(t *testing.T) { filter := getSeataFilter() - result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", []interface{}{"OK"}, map[string]string{ - SEATA_XID: "10.30.21.227:8091:2000047792", - })) + result := filter.Invoke(context.Background(), &testMockSeataInvoker{}, invocation.NewRPCInvocation("$echo", + []interface{}{"OK"}, map[string]interface{}{ + SEATA_XID: "10.30.21.227:8091:2000047792", + })) assert.Equal(t, "10.30.21.227:8091:2000047792", result.Result()) } diff --git a/filter/filter_impl/sentinel_filter.go b/filter/filter_impl/sentinel_filter.go new file mode 100644 index 0000000000000000000000000000000000000000..3787a0284f5470282c14c8b1a0aaf0187d6239e3 --- /dev/null +++ b/filter/filter_impl/sentinel_filter.go @@ -0,0 +1,255 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter_impl + +import ( + "context" + "fmt" + "strings" +) + +import ( + sentinel "github.com/alibaba/sentinel-golang/api" + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/logging" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/protocol" +) + +// Integrate Sentinel Go MUST HAVE: +// 1. Must initialize Sentinel Go run environment, +// refer to https://github.com/alibaba/sentinel-golang/blob/master/api/init.go +// 2. Register rules for resources user want to guard + +func init() { + extension.SetFilter(SentinelProviderFilterName, GetSentinelProviderFilter) + extension.SetFilter(SentinelConsumerFilterName, GetSentinelConsumerFilter) + + if err := logging.ResetGlobalLogger(DubboLoggerWrapper{Logger: logger.GetLogger()}); err != nil { + logger.Errorf("[Sentinel Filter] fail to ingest dubbo logger into sentinel") + } +} + +type DubboLoggerWrapper struct { + logger.Logger +} + +func (d DubboLoggerWrapper) Debug(msg string, keysAndValues ...interface{}) { + d.Logger.Debug(logging.AssembleMsg(logging.GlobalCallerDepth, "DEBUG", msg, nil, keysAndValues)) +} + +func (d DubboLoggerWrapper) DebugEnabled() bool { + return true +} + +func (d DubboLoggerWrapper) Info(msg string, keysAndValues ...interface{}) { + d.Logger.Info(logging.AssembleMsg(logging.GlobalCallerDepth, "INFO", msg, nil, keysAndValues)) +} + +func (d DubboLoggerWrapper) InfoEnabled() bool { + return true +} + +func (d DubboLoggerWrapper) Warn(msg string, keysAndValues ...interface{}) { + d.Logger.Warn(logging.AssembleMsg(logging.GlobalCallerDepth, "WARN", msg, nil, keysAndValues)) +} + +func (d DubboLoggerWrapper) WarnEnabled() bool { + return true +} + +func (d DubboLoggerWrapper) Error(err error, msg string, keysAndValues ...interface{}) { + d.Logger.Warn(logging.AssembleMsg(logging.GlobalCallerDepth, "ERROR", msg, err, keysAndValues)) +} + +func (d DubboLoggerWrapper) ErrorEnabled() bool { + return true +} + +func GetSentinelConsumerFilter() filter.Filter { + return &SentinelConsumerFilter{} +} + +func GetSentinelProviderFilter() filter.Filter { + return &SentinelProviderFilter{} +} + +func sentinelExit(ctx context.Context, result protocol.Result) { + if methodEntry := ctx.Value(MethodEntryKey); methodEntry != nil { + e := methodEntry.(*base.SentinelEntry) + sentinel.TraceError(e, result.Error()) + e.Exit() + } + if interfaceEntry := ctx.Value(InterfaceEntryKey); interfaceEntry != nil { + e := interfaceEntry.(*base.SentinelEntry) + sentinel.TraceError(e, result.Error()) + e.Exit() + } +} + +type SentinelProviderFilter struct{} + +func (d *SentinelProviderFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + interfaceResourceName, methodResourceName := getResourceName(invoker, invocation, getProviderPrefix()) + + var ( + interfaceEntry *base.SentinelEntry + methodEntry *base.SentinelEntry + b *base.BlockError + ) + interfaceEntry, b = sentinel.Entry(interfaceResourceName, sentinel.WithResourceType(base.ResTypeRPC), sentinel.WithTrafficType(base.Inbound)) + if b != nil { + // interface blocked + return sentinelDubboProviderFallback(ctx, invoker, invocation, b) + } + ctx = context.WithValue(ctx, InterfaceEntryKey, interfaceEntry) + + methodEntry, b = sentinel.Entry(methodResourceName, + sentinel.WithResourceType(base.ResTypeRPC), + sentinel.WithTrafficType(base.Inbound), + sentinel.WithArgs(invocation.Arguments()...)) + if b != nil { + // method blocked + return sentinelDubboProviderFallback(ctx, invoker, invocation, b) + } + ctx = context.WithValue(ctx, MethodEntryKey, methodEntry) + return invoker.Invoke(ctx, invocation) +} + +func (d *SentinelProviderFilter) OnResponse(ctx context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { + sentinelExit(ctx, result) + return result +} + +type SentinelConsumerFilter struct{} + +func (d *SentinelConsumerFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + interfaceResourceName, methodResourceName := getResourceName(invoker, invocation, getConsumerPrefix()) + var ( + interfaceEntry *base.SentinelEntry + methodEntry *base.SentinelEntry + b *base.BlockError + ) + + interfaceEntry, b = sentinel.Entry(interfaceResourceName, sentinel.WithResourceType(base.ResTypeRPC), sentinel.WithTrafficType(base.Outbound)) + if b != nil { + // interface blocked + return sentinelDubboConsumerFallback(ctx, invoker, invocation, b) + } + ctx = context.WithValue(ctx, InterfaceEntryKey, interfaceEntry) + + methodEntry, b = sentinel.Entry(methodResourceName, sentinel.WithResourceType(base.ResTypeRPC), + sentinel.WithTrafficType(base.Outbound), sentinel.WithArgs(invocation.Arguments()...)) + if b != nil { + // method blocked + return sentinelDubboConsumerFallback(ctx, invoker, invocation, b) + } + ctx = context.WithValue(ctx, MethodEntryKey, methodEntry) + + return invoker.Invoke(ctx, invocation) +} + +func (d *SentinelConsumerFilter) OnResponse(ctx context.Context, result protocol.Result, _ protocol.Invoker, _ protocol.Invocation) protocol.Result { + sentinelExit(ctx, result) + return result +} + +var ( + sentinelDubboConsumerFallback = getDefaultDubboFallback() + sentinelDubboProviderFallback = getDefaultDubboFallback() +) + +type DubboFallback func(context.Context, protocol.Invoker, protocol.Invocation, *base.BlockError) protocol.Result + +func SetDubboConsumerFallback(f DubboFallback) { + sentinelDubboConsumerFallback = f +} +func SetDubboProviderFallback(f DubboFallback) { + sentinelDubboProviderFallback = f +} +func getDefaultDubboFallback() DubboFallback { + return func(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, blockError *base.BlockError) protocol.Result { + result := &protocol.RPCResult{} + result.SetResult(nil) + result.SetError(blockError) + return result + } +} + +const ( + SentinelProviderFilterName = "sentinel-provider" + SentinelConsumerFilterName = "sentinel-consumer" + + DefaultProviderPrefix = "dubbo:provider:" + DefaultConsumerPrefix = "dubbo:consumer:" + + MethodEntryKey = "$$sentinelMethodEntry" + InterfaceEntryKey = "$$sentinelInterfaceEntry" +) + +func getResourceName(invoker protocol.Invoker, invocation protocol.Invocation, prefix string) (interfaceResourceName, methodResourceName string) { + var sb strings.Builder + + sb.WriteString(prefix) + if getInterfaceGroupAndVersionEnabled() { + interfaceResourceName = getColonSeparatedKey(invoker.GetUrl()) + } else { + interfaceResourceName = invoker.GetUrl().Service() + } + sb.WriteString(interfaceResourceName) + sb.WriteString(":") + sb.WriteString(invocation.MethodName()) + sb.WriteString("(") + isFirst := true + for _, v := range invocation.ParameterTypes() { + if !isFirst { + sb.WriteString(",") + } + sb.WriteString(v.Name()) + isFirst = false + } + sb.WriteString(")") + methodResourceName = sb.String() + return +} + +func getConsumerPrefix() string { + return DefaultConsumerPrefix +} + +func getProviderPrefix() string { + return DefaultProviderPrefix +} + +func getInterfaceGroupAndVersionEnabled() bool { + return true +} + +func getColonSeparatedKey(url *common.URL) string { + return fmt.Sprintf("%s:%s:%s", + url.Service(), + url.GetParam(constant.GROUP_KEY, ""), + url.GetParam(constant.VERSION_KEY, "")) +} diff --git a/filter/filter_impl/sentinel_filter_test.go b/filter/filter_impl/sentinel_filter_test.go new file mode 100644 index 0000000000000000000000000000000000000000..a1ca21ce6c634b9647e586df0ac97a51d3fae7cc --- /dev/null +++ b/filter/filter_impl/sentinel_filter_test.go @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter_impl + +import ( + "context" + "sync" + "sync/atomic" + "testing" +) + +import ( + "github.com/alibaba/sentinel-golang/core/flow" + "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" +) + +func TestSentinelFilter_QPS(t *testing.T) { + url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + + "version=1.0.0&group=myGroup&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + assert.NoError(t, err) + mockInvoker := protocol.NewBaseInvoker(url) + interfaceResourceName, _ := getResourceName(mockInvoker, + invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]interface{})), "prefix_") + mockInvocation := invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]interface{})) + + _, err = flow.LoadRules([]*flow.Rule{ + { + Resource: interfaceResourceName, + //MetricType: flow.QPS, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + Threshold: 100, + RelationStrategy: flow.CurrentResource, + }, + }) + assert.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(10) + f := GetSentinelProviderFilter() + pass := int64(0) + block := int64(0) + for i := 0; i < 10; i++ { + go func() { + for j := 0; j < 30; j++ { + result := f.Invoke(context.TODO(), mockInvoker, mockInvocation) + if result.Error() == nil { + atomic.AddInt64(&pass, 1) + } else { + atomic.AddInt64(&block, 1) + } + } + wg.Done() + }() + } + wg.Wait() + assert.True(t, atomic.LoadInt64(&pass) == 100) + assert.True(t, atomic.LoadInt64(&block) == 200) +} + +func TestConsumerFilter_Invoke(t *testing.T) { + f := GetSentinelConsumerFilter() + url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + assert.NoError(t, err) + mockInvoker := protocol.NewBaseInvoker(url) + mockInvocation := invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]interface{})) + result := f.Invoke(context.TODO(), mockInvoker, mockInvocation) + assert.NoError(t, result.Error()) +} + +func TestProviderFilter_Invoke(t *testing.T) { + f := GetSentinelProviderFilter() + url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + assert.NoError(t, err) + mockInvoker := protocol.NewBaseInvoker(url) + mockInvocation := invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]interface{})) + result := f.Invoke(context.TODO(), mockInvoker, mockInvocation) + assert.NoError(t, result.Error()) +} + +func TestGetResourceName(t *testing.T) { + url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + + "version=1.0.0&group=myGroup&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + assert.NoError(t, err) + mockInvoker := protocol.NewBaseInvoker(url) + interfaceResourceName, methodResourceName := getResourceName(mockInvoker, + invocation.NewRPCInvocation("hello", []interface{}{"OK"}, make(map[string]interface{})), "prefix_") + assert.Equal(t, "com.ikurento.user.UserProvider:myGroup:1.0.0", interfaceResourceName) + assert.Equal(t, "prefix_com.ikurento.user.UserProvider:myGroup:1.0.0:hello()", methodResourceName) +} diff --git a/filter/filter_impl/token_filter.go b/filter/filter_impl/token_filter.go index fe4e38747ed10a40950c87747220339d0d566781..b5e05605c29905d9e66a63129f064c2f844f0e71 100644 --- a/filter/filter_impl/token_filter.go +++ b/filter/filter_impl/token_filter.go @@ -51,7 +51,7 @@ func (tf *TokenFilter) Invoke(ctx context.Context, invoker protocol.Invoker, inv if len(invokerTkn) > 0 { attachs := invocation.Attachments() remoteTkn, exist := attachs[constant.TOKEN_KEY] - if exist && strings.EqualFold(invokerTkn, remoteTkn) { + if exist && remoteTkn != nil && strings.EqualFold(invokerTkn, remoteTkn.(string)) { return invoker.Invoke(ctx, invocation) } return &protocol.RPCResult{Err: perrors.Errorf("Invalid token! Forbid invoke remote service %v method %s ", diff --git a/filter/filter_impl/token_filter_test.go b/filter/filter_impl/token_filter_test.go index c2f69bd03941b1404585dc5842c56eb2bf3c918f..9ef8c98d6868c3d545fe963ab13eb3f27bb88cd0 100644 --- a/filter/filter_impl/token_filter_test.go +++ b/filter/filter_impl/token_filter_test.go @@ -40,10 +40,10 @@ func TestTokenFilterInvoke(t *testing.T) { url := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) attch[constant.TOKEN_KEY] = "ori_key" result := filter.Invoke(context.Background(), - protocol.NewBaseInvoker(*url), + protocol.NewBaseInvoker(url), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) @@ -54,9 +54,9 @@ func TestTokenFilterInvokeEmptyToken(t *testing.T) { filter := GetTokenFilter() testUrl := common.URL{} - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) attch[constant.TOKEN_KEY] = "ori_key" - result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(testUrl), 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()) } @@ -67,8 +67,8 @@ func TestTokenFilterInvokeEmptyAttach(t *testing.T) { 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(*testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + attch := make(map[string]interface{}, 0) + result := filter.Invoke(context.Background(), protocol.NewBaseInvoker(testUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.NotNil(t, result.Error()) } @@ -78,9 +78,9 @@ func TestTokenFilterInvokeNotEqual(t *testing.T) { testUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TOKEN_KEY, "ori_key")) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) attch[constant.TOKEN_KEY] = "err_key" result := filter.Invoke(context.Background(), - protocol.NewBaseInvoker(*testUrl), 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_limiter_method_service.go b/filter/filter_impl/tps/tps_limiter_method_service.go index 5761579a38a22500d54193a9564170cc0215cf0f..f0c276438beaa161a6f6b13bc95c1fd7f357e8b6 100644 --- a/filter/filter_impl/tps/tps_limiter_method_service.go +++ b/filter/filter_impl/tps/tps_limiter_method_service.go @@ -120,7 +120,7 @@ type MethodServiceTpsLimiterImpl struct { // 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 { +func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url *common.URL, invocation protocol.Invocation) bool { methodConfigPrefix := "methods." + invocation.MethodName() + "." @@ -176,7 +176,7 @@ func (limiter MethodServiceTpsLimiterImpl) IsAllowable(url common.URL, invocatio // 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, + url *common.URL, invocation protocol.Invocation, configKey string, defaultVal string) int64 { diff --git a/filter/filter_impl/tps/tps_limiter_method_service_test.go b/filter/filter_impl/tps/tps_limiter_method_service_test.go index edae99ec2d3157ad7f0d81c95a2fb181410475fa..7435d9b92ac8270de0244c8a257aec82f9acc9a1 100644 --- a/filter/filter_impl/tps/tps_limiter_method_service_test.go +++ b/filter/filter_impl/tps/tps_limiter_method_service_test.go @@ -36,7 +36,7 @@ import ( func TestMethodServiceTpsLimiterImplIsAllowableOnlyServiceLevel(t *testing.T) { methodName := "hello" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -57,13 +57,13 @@ func TestMethodServiceTpsLimiterImplIsAllowableOnlyServiceLevel(t *testing.T) { }) limiter := GetMethodServiceTpsLimiter() - result := limiter.IsAllowable(*invokeUrl, invoc) + result := limiter.IsAllowable(invokeUrl, invoc) assert.True(t, result) } func TestMethodServiceTpsLimiterImplIsAllowableNoConfig(t *testing.T) { methodName := "hello1" - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) // ctrl := gomock.NewController(t) // defer ctrl.Finish() @@ -73,14 +73,14 @@ func TestMethodServiceTpsLimiterImplIsAllowableNoConfig(t *testing.T) { common.WithParamsValue(constant.TPS_LIMIT_RATE_KEY, "")) limiter := GetMethodServiceTpsLimiter() - result := limiter.IsAllowable(*invokeUrl, invoc) + result := limiter.IsAllowable(invokeUrl, invoc) assert.True(t, result) } func TestMethodServiceTpsLimiterImplIsAllowableMethodLevelOverride(t *testing.T) { methodName := "hello2" methodConfigPrefix := "methods." + methodName + "." - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -106,14 +106,14 @@ func TestMethodServiceTpsLimiterImplIsAllowableMethodLevelOverride(t *testing.T) }) limiter := GetMethodServiceTpsLimiter() - result := limiter.IsAllowable(*invokeUrl, invoc) + result := limiter.IsAllowable(invokeUrl, invoc) assert.True(t, result) } func TestMethodServiceTpsLimiterImplIsAllowableBothMethodAndService(t *testing.T) { methodName := "hello3" methodConfigPrefix := "methods." + methodName + "." - invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]string, 0)) + invoc := invocation.NewRPCInvocation(methodName, []interface{}{"OK"}, make(map[string]interface{}, 0)) ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -136,7 +136,7 @@ func TestMethodServiceTpsLimiterImplIsAllowableBothMethodAndService(t *testing.T }) limiter := GetMethodServiceTpsLimiter() - result := limiter.IsAllowable(*invokeUrl, invoc) + result := limiter.IsAllowable(invokeUrl, invoc) assert.True(t, result) } diff --git a/filter/filter_impl/tps/tps_limiter_mock.go b/filter/filter_impl/tps/tps_limiter_mock.go index b49084f28e7d4b62d64762170b6dfbbec4a4de96..34c279032713595066638095b5f4d2f6fa2c4aa4 100644 --- a/filter/filter_impl/tps/tps_limiter_mock.go +++ b/filter/filter_impl/tps/tps_limiter_mock.go @@ -58,7 +58,7 @@ func (m *MockTpsLimiter) EXPECT() *MockTpsLimiterMockRecorder { } // IsAllowable mocks base method -func (m *MockTpsLimiter) IsAllowable(arg0 common.URL, arg1 protocol.Invocation) bool { +func (m *MockTpsLimiter) IsAllowable(arg0 *common.URL, arg1 protocol.Invocation) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsAllowable", arg0, arg1) ret0, _ := ret[0].(bool) diff --git a/filter/filter_impl/tps_limit_filter_test.go b/filter/filter_impl/tps_limit_filter_test.go index 274e4e6de61b94079e9ad3b2f7a5bcd79a276cc6..88e778105081129463b68618804bd9204d2f7113 100644 --- a/filter/filter_impl/tps_limit_filter_test.go +++ b/filter/filter_impl/tps_limit_filter_test.go @@ -44,10 +44,10 @@ func TestTpsLimitFilterInvokeWithNoTpsLimiter(t *testing.T) { invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TPS_LIMITER_KEY, "")) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) result := tpsFilter.Invoke(context.Background(), - protocol.NewBaseInvoker(*invokeUrl), + protocol.NewBaseInvoker(invokeUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) @@ -68,10 +68,10 @@ func TestGenericFilterInvokeWithDefaultTpsLimiter(t *testing.T) { invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TPS_LIMITER_KEY, constant.DEFAULT_KEY)) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) result := tpsFilter.Invoke(context.Background(), - protocol.NewBaseInvoker(*invokeUrl), + protocol.NewBaseInvoker(invokeUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) @@ -99,10 +99,12 @@ func TestGenericFilterInvokeWithDefaultTpsLimiterNotAllow(t *testing.T) { invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.TPS_LIMITER_KEY, constant.DEFAULT_KEY)) - attch := make(map[string]string, 0) + attch := make(map[string]interface{}, 0) result := tpsFilter.Invoke(context.Background(), - protocol.NewBaseInvoker(*invokeUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) + protocol.NewBaseInvoker( + + invokeUrl), invocation.NewRPCInvocation("MethodName", []interface{}{"OK"}, attch)) assert.Nil(t, result.Error()) assert.Nil(t, result.Result()) } diff --git a/filter/filter_impl/tracing_filter.go b/filter/filter_impl/tracing_filter.go index b8058aa601af98b5416da882321546675459c413..dcdbe5b30e3c288462e9078df234243435ca86c6 100644 --- a/filter/filter_impl/tracing_filter.go +++ b/filter/filter_impl/tracing_filter.go @@ -83,7 +83,7 @@ func (tf *tracingFilter) Invoke(ctx context.Context, invoker protocol.Invoker, i }() result := invoker.Invoke(spanCtx, invocation) - span.SetTag(successKey, result.Error() != nil) + span.SetTag(successKey, result.Error() == nil) if result.Error() != nil { span.LogFields(log.String(errorKey, result.Error().Error())) } diff --git a/filter/filter_impl/tracing_filter_test.go b/filter/filter_impl/tracing_filter_test.go index 57f4095d49be784d7688d2acf17c1ea0225d0000..e159b7400d46069018a00a849319423285072dc2 100644 --- a/filter/filter_impl/tracing_filter_test.go +++ b/filter/filter_impl/tracing_filter_test.go @@ -42,7 +42,7 @@ func TestTracingFilterInvoke(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) ctx := context.Background() tf := newTracingFilter() diff --git a/filter/handler/rejected_execution_handler_mock.go b/filter/handler/rejected_execution_handler_mock.go index bff54769cb007ed2fce5a7623c96edc370e63903..5f2f458f83d0ca2cdff68eec4d0f3d22cf4e3277 100644 --- a/filter/handler/rejected_execution_handler_mock.go +++ b/filter/handler/rejected_execution_handler_mock.go @@ -58,7 +58,7 @@ func (m *MockRejectedExecutionHandler) EXPECT() *MockRejectedExecutionHandlerMoc } // RejectedExecution mocks base method -func (m *MockRejectedExecutionHandler) RejectedExecution(url common.URL, invocation protocol.Invocation) protocol.Result { +func (m *MockRejectedExecutionHandler) RejectedExecution(url *common.URL, invocation protocol.Invocation) protocol.Result { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RejectedExecution", url, invocation) ret0, _ := ret[0].(protocol.Result) diff --git a/filter/handler/rejected_execution_handler_only_log.go b/filter/handler/rejected_execution_handler_only_log.go index 52ac1765f78172c0062de8884198e759b8d494ca..5242b5b49e8a12322821481d237d84710c15eafd 100644 --- a/filter/handler/rejected_execution_handler_only_log.go +++ b/filter/handler/rejected_execution_handler_only_log.go @@ -63,7 +63,7 @@ type OnlyLogRejectedExecutionHandler struct { } // RejectedExecution will do nothing, it only log the invocation. -func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url common.URL, +func (handler *OnlyLogRejectedExecutionHandler) RejectedExecution(url *common.URL, _ protocol.Invocation) protocol.Result { logger.Errorf("The invocation was rejected. url: %s", url.String()) diff --git a/filter/handler/rejected_execution_handler_only_log_test.go b/filter/handler/rejected_execution_handler_only_log_test.go index 409f09f61bd958992749231fca045b54601fc627..7aa4aff936d6da1e46ed5a24609a0346e9d2edb4 100644 --- a/filter/handler/rejected_execution_handler_only_log_test.go +++ b/filter/handler/rejected_execution_handler_only_log_test.go @@ -31,5 +31,5 @@ func TestOnlyLogRejectedExecutionHandler_RejectedExecution(t *testing.T) { invokeUrl := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.INTERFACE_KEY, "methodName")) - handler.RejectedExecution(*invokeUrl, nil) + handler.RejectedExecution(invokeUrl, nil) } diff --git a/filter/rejected_execution_handler.go b/filter/rejected_execution_handler.go index 3d1e1c1e641a836411ce0f71f97acf5d5a55f6d1..73ac3d3bbf37cbce5822e1955fb49f84bdf6d678 100644 --- a/filter/rejected_execution_handler.go +++ b/filter/rejected_execution_handler.go @@ -33,5 +33,5 @@ import ( type RejectedExecutionHandler interface { // RejectedExecution will be called if the invocation was rejected by some component. - RejectedExecution(url common.URL, invocation protocol.Invocation) protocol.Result + RejectedExecution(url *common.URL, invocation protocol.Invocation) protocol.Result } diff --git a/filter/tps_limiter.go b/filter/tps_limiter.go index 8385d7b5d84a420b54df6bf51e32a35d17e1b249..6f2466c0a40620b9f7dee358db681c4032c773fc 100644 --- a/filter/tps_limiter.go +++ b/filter/tps_limiter.go @@ -35,5 +35,5 @@ import ( */ type TpsLimiter interface { // IsAllowable will check whether this invocation should be enabled for further process - IsAllowable(common.URL, protocol.Invocation) bool + IsAllowable(*common.URL, protocol.Invocation) bool } diff --git a/go.mod b/go.mod index 3b622d924800a62c1774965ef1d14ce4928796f6..e822ab4cf352c5808747f358dc831071cf6a4906 100644 --- a/go.mod +++ b/go.mod @@ -1,64 +1,58 @@ module github.com/apache/dubbo-go +go 1.15 + require ( - github.com/Microsoft/go-winio v0.4.13 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/Workiva/go-datastructures v1.0.50 + github.com/RoaringBitmap/roaring v0.5.5 + github.com/Workiva/go-datastructures v1.0.52 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 + github.com/alibaba/sentinel-golang v1.0.1 github.com/apache/dubbo-getty v1.3.10 - github.com/apache/dubbo-go-hessian2 v1.6.2 - github.com/coreos/bbolt v1.3.3 // indirect - github.com/coreos/etcd v3.3.13+incompatible - github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect - github.com/creasty/defaults v1.3.0 - github.com/docker/go-connections v0.4.0 // indirect - github.com/dubbogo/go-zookeeper v1.0.1 - github.com/dubbogo/gost v1.9.1 + github.com/apache/dubbo-go-hessian2 v1.8.0 + github.com/coreos/etcd v3.3.25+incompatible + github.com/creasty/defaults v1.5.1 + github.com/dubbogo/go-zookeeper v1.0.2 + github.com/dubbogo/gost v1.9.5 github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect - github.com/emicklei/go-restful/v3 v3.0.0 + github.com/emicklei/go-restful/v3 v3.4.0 github.com/frankban/quicktest v1.4.1 // indirect - github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect + github.com/fsnotify/fsnotify v1.4.9 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 - github.com/golang/protobuf v1.3.2 - github.com/google/go-cmp v0.3.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.9.5 // indirect + github.com/go-resty/resty/v2 v2.3.0 + github.com/golang/mock v1.4.4 + github.com/golang/protobuf v1.4.3 github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/hashicorp/consul v1.8.0 github.com/hashicorp/consul/api v1.5.0 github.com/hashicorp/go-raftchunking v0.6.3-0.20191002164813-7e9e8525653a // indirect - github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/hashicorp/vault/api v1.0.5-0.20191108163347-bdd38fca2cff // indirect github.com/hashicorp/vault/sdk v0.1.14-0.20191112033314-390e96e22eb2 github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8 - github.com/magiconair/properties v1.8.1 - github.com/mitchellh/hashstructure v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.2.3 + github.com/magiconair/properties v1.8.4 + github.com/mitchellh/mapstructure v1.4.0 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd - github.com/nacos-group/nacos-sdk-go v1.0.0 - github.com/opentracing/opentracing-go v1.1.0 + github.com/nacos-group/nacos-sdk-go v1.0.1 + github.com/opentracing/opentracing-go v1.2.0 github.com/pierrec/lz4 v2.2.6+incompatible // indirect github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.1.0 + github.com/prometheus/client_golang v1.8.0 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b - github.com/shirou/gopsutil v2.19.9+incompatible // indirect github.com/stretchr/objx v0.2.0 // indirect - github.com/stretchr/testify v1.5.1 - github.com/zouyx/agollo/v3 v3.4.4 - go.etcd.io/bbolt v1.3.4 // indirect - go.uber.org/atomic v1.6.0 - go.uber.org/zap v1.15.0 - google.golang.org/grpc v1.23.0 - gopkg.in/yaml.v2 v2.2.8 + github.com/stretchr/testify v1.6.1 + github.com/zouyx/agollo/v3 v3.4.5 + go.uber.org/atomic v1.7.0 + go.uber.org/zap v1.16.0 + golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect + google.golang.org/grpc v1.26.0 + gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.16.9 k8s.io/apimachinery v0.16.9 k8s.io/client-go v0.16.9 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect ) -go 1.13 - -replace launchpad.net/gocheck => github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a +replace ( + github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.4 + github.com/envoyproxy/go-control-plane => github.com/envoyproxy/go-control-plane v0.8.0 +) diff --git a/go.sum b/go.sum index 91cdb0da19ca1acd9d35b630a65a9d8596382910..7b1c1716aa6701a495dbed2f54d411927c7bd890 100644 --- a/go.sum +++ b/go.sum @@ -48,9 +48,9 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.4.3 h1:M3NHMuPgMSUPdE5epwNUHlRPSVzHs8HpRTrVXhR0myo= github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.13 h1:Hmi80lzZuI/CaYmlJp/b+FjZdRZhKu9c2mDVqKlLWVs= -github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= @@ -58,22 +58,34 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RoaringBitmap/roaring v0.5.5 h1:naNqvO1mNnghk2UvcsqnzHDBn9DRbCIRy94GmDTRVTQ= +github.com/RoaringBitmap/roaring v0.5.5/go.mod h1:puNo5VdzwbaIQxSiDIwfXl4Hnc+fbovcX4IW/dSTtUk= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo= -github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/Workiva/go-datastructures v1.0.52 h1:PLSK6pwn8mYdaoaCZEMsXBpBotr4HHn9abU0yMQt0NI= +github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= 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/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alibaba/sentinel-golang v1.0.1 h1:WlhN0XUxRyfkiDc8TO6CcRrnakwFP9zFtvJTYxZRCgI= +github.com/alibaba/sentinel-golang v1.0.1/go.mod h1:QsB99f/z35D2AiMrAWwgWE85kDTkBUIkcmPrRt+61NI= 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-getty v1.3.10 h1:ys5mwjPdxG/KwkPjS6EI0RzQtU6p6FCPoKpaFEzpAL0= github.com/apache/dubbo-getty v1.3.10/go.mod h1:x6rraK01BL5C7jUM2fPl5KMkAxLVIx54ZB8/XEOik9Y= -github.com/apache/dubbo-go-hessian2 v1.6.2 h1:i7F5GjVaUatLQz1x9vUmmSIFj49L8J6rVICdF6xw4qw= -github.com/apache/dubbo-go-hessian2 v1.6.2/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/apache/dubbo-go-hessian2 v1.8.0 h1:+GJQHxWd/WUw2p4hbfCal/zjKvGVb8yJZzOke8IEazc= +github.com/apache/dubbo-go-hessian2 v1.8.0/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 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/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -84,8 +96,12 @@ github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.25.41 h1:/hj7nZ0586wFqpwjNpzWiUTwtaMgxAZNZKHay80MdXw= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= 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= @@ -97,35 +113,44 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coredns/coredns v1.1.2 h1:bAFHrSsBeTeRG5W3Nf2su3lUGw7Npw2UKeCJm/3A638= github.com/coredns/coredns v1.1.2/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= -github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.25+incompatible h1:0GQEw6h3YnuOVdtwygkIfJ+Omx0tZ8/QkVyXI4LkbeY= +github.com/coreos/etcd v3.3.25+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/creasty/defaults v1.3.0 h1:uG+RAxYbJgOPCOdKEcec9ZJXeva7Y6mj/8egdzwmLtw= -github.com/creasty/defaults v1.3.0/go.mod h1:CIEEvs7oIVZm30R8VxtFJs+4k201gReYyuYHJxZc68I= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creasty/defaults v1.5.1 h1:j8WexcS3d/t4ZmllX4GEkl4wIB/trOr035ajcLHCISM= +github.com/creasty/defaults v1.5.1/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -142,29 +167,34 @@ github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TR github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dubbogo/go-zookeeper v1.0.1 h1:irLzvOsDOTNsN8Sv9tvYYxVu6DCQfLtziZQtUHmZgz8= -github.com/dubbogo/go-zookeeper v1.0.1/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= -github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk= +github.com/dubbogo/go-zookeeper v1.0.2 h1:xmEnPL8SlCe3/+J5ZR9e8qE35LmFVYe8VVpDakjNM4A= +github.com/dubbogo/go-zookeeper v1.0.2/go.mod h1:fn6n2CAEer3novYgk9ULLwAjuV8/g4DdC2ENwRb6E+c= github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= -github.com/dubbogo/gost v1.9.1 h1:0/PPFo13zPbjt4Ia0zYWMFi3C6rAe9X7O1J2Iv+BHNM= -github.com/dubbogo/gost v1.9.1/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/dubbogo/gost v1.9.5 h1:UeG4y0O55lR3dzgdmCm/7bMWvpKrlpR7fsfKjrcXq/g= +github.com/dubbogo/gost v1.9.5/go.mod h1:QNM5RaeRdNWehUu8S0hUP5Qa8QUfGf6KH1JhqOVFvEI= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/go-bindata-assetfs v0.0.0-20160803192304-e1a2a7ec64b0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.0.0 h1:Duxxa4x0WIHW3bYEDmoAPNjmy8Rbqn+utcF74dlF/G8= -github.com/emicklei/go-restful/v3 v3.0.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.4.0 h1:IIDhql3oyWZj1ay2xBZGb4sTOWMad0HVW8rwhVxN/Yk= +github.com/emicklei/go-restful/v3 v3.4.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.8.0 h1:uE6Fp4fOcAJdc1wTQXLJ+SYistkbG1dNoi6Zs1+Ybvk= github.com/envoyproxy/go-control-plane v0.8.0/go.mod h1:GSSbY9P1neVhdY7G4wu+IK1rk/dqhiCC/4ExuWJZVuk= -github.com/envoyproxy/protoc-gen-validate v0.0.14 h1:YBW6/cKy9prEGRYLnaGa4IDhzxZhRCtKsax8srGKDnM= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= @@ -173,26 +203,33 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg= github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4= +github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-check/check v0.0.0-20140225173054-eb6ee6f84d0a/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= 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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-ldap/ldap/v3 v3.1.3/go.mod h1:3rbOH3jRS2u6jg2rJnKAMLE/xQyCKIveG2Sa/Cohzb8= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= @@ -201,8 +238,9 @@ github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1 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-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= +github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= @@ -210,6 +248,7 @@ github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -222,13 +261,22 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -236,8 +284,9 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= @@ -250,6 +299,8 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 h1:AtvtonGEH/fZK0XPNNBdB6swgy7Iudfx88wzyIpwqJ8= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= @@ -259,8 +310,13 @@ github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhp github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -278,9 +334,11 @@ github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go github.com/hashicorp/consul v1.8.0 h1:yRKMKZyPLqUxl37t4nFt5OuGmTXoFhTJrakhfnYKCYA= github.com/hashicorp/consul v1.8.0/go.mod h1:Gg9/UgAQ9rdY3CTvzQZ6g2jcIb7NlIfjI+0pvLk5D1A= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.5.0 h1:Yo2bneoGy68A7aNwmuETFnPhjyBEm7n3vzRacEVMjvI= github.com/hashicorp/consul/api v1.5.0/go.mod h1:LqwrLNW876eYSuUOo4ZLHBcdKc038txr/IMfbLPATa4= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.5.0 h1:WC4594Wp/LkEeML/OdQKEC1yqBmEYkRp6i7X5u0zDAs= github.com/hashicorp/consul/sdk v0.5.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -339,9 +397,8 @@ github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+d github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 h1:uk280DXEbQiCOZgCOI3elFSeNxf8YIZiNsbr2pQLYD0= @@ -380,10 +437,12 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk= @@ -399,21 +458,28 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= @@ -428,22 +494,29 @@ github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linode/linodego v0.7.1 h1:4WZmMpSA2NRwlPZcc0+4Gyn7rr99Evk9bnr0B3gXRKE= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= +github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -464,14 +537,14 @@ github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452 h1:hOY53G+kBFhbYFpRVxHl5eS7laP6B1+Cq+Z9Dry1iMU= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= -github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y= -github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.2.3 h1:f/MjBEBDLttYCGfRaKBbKSRVF5aV2O6fnBpzknuE3jU= github.com/mitchellh/mapstructure v1.2.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -484,37 +557,66 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nacos-group/nacos-sdk-go v1.0.0 h1:CufUF7DZca2ZzIrJtMMCDih1sA58BWCglArLMCZArUc= -github.com/nacos-group/nacos-sdk-go v1.0.0/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= +github.com/nacos-group/nacos-sdk-go v1.0.1 h1:VNmXGlSS28xOmkO5Nxk5WRp6f1HMosAmG9pDtcnUFcw= +github.com/nacos-group/nacos-sdk-go v1.0.1/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 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/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.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.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/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/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c h1:vwpFWvAO8DeIZfFeqASzZfsxuWPno9ncAebBEP0N3uE= github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c/go.mod h1:otzZQXgoO96RTzDB/Hycg0qZcXZsWJGJRSXbmEIJ+4M= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw= github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -522,6 +624,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 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= @@ -532,37 +635,52 @@ github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAm github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= +github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rboyer/safeio v0.2.1/go.mod h1:Cq/cEPK+YXFn622lsQ0K4KsPZSPtaptHHEldsy7Fmig= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOekn6B5G/+GMtks9UKbvRU/CMM/o= github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 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= @@ -571,16 +689,16 @@ github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6 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/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil v2.19.9+incompatible h1:IrPVlK4nfwW10DF7pW+7YJKws9NkgNzWozwwWv9FsgY= -github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= +github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto= +github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -590,6 +708,7 @@ github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQv github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -597,17 +716,22 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= @@ -616,8 +740,9 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= @@ -627,6 +752,9 @@ github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible/go.mod h1:0PfYo github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8= github.com/tevid/gohamcrest v1.1.1 h1:ou+xSqlIw1xfGTg1uq1nif/htZ2S3EzRqLm2BP+tYU0= github.com/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k= +github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= @@ -635,30 +763,47 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= +github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/zouyx/agollo/v3 v3.4.4 h1:5G7QNw3fw74Ns8SfnHNhjndV2mlz5Fg8bB7q84ydFYI= -github.com/zouyx/agollo/v3 v3.4.4/go.mod h1:ag0XmE1r4iAgPd6PUnU9TJ0DMEjM1VKX1HUNqQJ2ywU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zouyx/agollo/v3 v3.4.5 h1:7YCxzY9ZYaH9TuVUBvmI6Tk0mwMggikah+cfbYogcHQ= +github.com/zouyx/agollo/v3 v3.4.5/go.mod h1:LJr3kDmm23QSW+F1Ol4TMHDa7HvJvscMdVxJ2IpUTVc= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 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.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -667,10 +812,13 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -684,12 +832,16 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -699,6 +851,7 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -708,11 +861,14 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/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/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -722,8 +878,9 @@ 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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= @@ -732,6 +889,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -748,27 +906,39 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -789,22 +959,29 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200928182047-19e03678916f h1:VwGa2Wf+rHGIxvsssCkUNIyFv8jQY0VCBCNWtikoWq0= +golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -815,21 +992,32 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= @@ -837,13 +1025,14 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -854,12 +1043,19 @@ gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -883,7 +1079,9 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLy k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/integrate_test.sh b/integrate_test.sh index c9c2f23b5b07f0baf96260d8092e7464d4d15659..deccda756a211821978e35b92a1f0865858ff59a 100644 --- a/integrate_test.sh +++ b/integrate_test.sh @@ -63,4 +63,4 @@ docker build . -t ci-consumer --build-arg PR_ORIGIN_REPO=${TRAVIS_PULL_REQUEST_ cd ${ROOT_DIR} # run provider # check consumer status -docker run -it --network host ci-consumer +docker run -i --network host ci-consumer diff --git a/metadata/definition/definition.go b/metadata/definition/definition.go index dbbc0c8f1685edf1a26ab1fe4ad091c501e76f5f..a0323137b70ff6ea34249b10e82d4f5cd84c9cb0 100644 --- a/metadata/definition/definition.go +++ b/metadata/definition/definition.go @@ -93,7 +93,7 @@ type TypeDefinition struct { } // BuildServiceDefinition can build service definition which will be used to describe a service -func BuildServiceDefinition(service common.Service, url common.URL) *ServiceDefinition { +func BuildServiceDefinition(service common.Service, url *common.URL) *ServiceDefinition { sd := &ServiceDefinition{} sd.CanonicalName = url.Service() diff --git a/metadata/definition/definition_test.go b/metadata/definition/definition_test.go index 958f9324d0f9f4a5027792bd8e54b238a5f56feb..389159239b4f4d7156983c83096ead151561ad28 100644 --- a/metadata/definition/definition_test.go +++ b/metadata/definition/definition_test.go @@ -28,7 +28,6 @@ import ( import ( "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" ) func TestBuildServiceDefinition(t *testing.T) { @@ -44,9 +43,9 @@ func TestBuildServiceDefinition(t *testing.T) { "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{}) + _, err = common.ServiceMap.Register(serviceName, protocol, group, version, &UserProvider{}) assert.NoError(t, err) - service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) + service := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey()) 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/identifier/service_metadata_identifier.go b/metadata/identifier/service_metadata_identifier.go index b9e65967e0f707a6efcc9f8ded2ce5dec4f058b8..3035cf3d720bd453baecb147c605f1bfc928d825 100644 --- a/metadata/identifier/service_metadata_identifier.go +++ b/metadata/identifier/service_metadata_identifier.go @@ -32,7 +32,7 @@ type ServiceMetadataIdentifier struct { // NewServiceMetadataIdentifier create instance. // The ServiceInterface is the @url.Service() // other parameters are read from @url -func NewServiceMetadataIdentifier(url common.URL) *ServiceMetadataIdentifier { +func NewServiceMetadataIdentifier(url *common.URL) *ServiceMetadataIdentifier { return &ServiceMetadataIdentifier{ BaseMetadataIdentifier: BaseMetadataIdentifier{ ServiceInterface: url.Service(), diff --git a/metadata/report/consul/report.go b/metadata/report/consul/report.go index eb2bdc25ecec596a8f89abb80856b8d6e7be70a4..e211f7fde4d462bd20603af54444036681a46f4d 100644 --- a/metadata/report/consul/report.go +++ b/metadata/report/consul/report.go @@ -61,7 +61,7 @@ func (m *consulMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier } // SaveServiceMetadata saves the metadata. -func (m *consulMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error { +func (m *consulMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url *common.URL) error { kv := &consul.KVPair{Key: metadataIdentifier.GetIdentifierKey(), Value: []byte(url.String())} _, err := m.client.KV().Put(kv, nil) return err diff --git a/metadata/report/consul/report_test.go b/metadata/report/consul/report_test.go index e07a7429f12bac0cfa4b2701d3e87ef25bcb077a..13d0c419bc0f0ff8426f73d4de5c85a346416770 100644 --- a/metadata/report/consul/report_test.go +++ b/metadata/report/consul/report_test.go @@ -100,7 +100,7 @@ func (suite *consulMetadataReportTestSuite) testStoreConsumerMetadata() { assert.NoError(suite.t, err) } -func (suite *consulMetadataReportTestSuite) testSaveServiceMetadata(url common.URL) { +func (suite *consulMetadataReportTestSuite) testSaveServiceMetadata(url *common.URL) { serviceMi := newServiceMetadataIdentifier("provider") err := suite.m.SaveServiceMetadata(serviceMi, url) assert.NoError(suite.t, err) @@ -119,7 +119,7 @@ func (suite *consulMetadataReportTestSuite) testGetExportedURLs() { assert.NoError(suite.t, err) } -func (suite *consulMetadataReportTestSuite) testSaveSubscribedData(url common.URL) { +func (suite *consulMetadataReportTestSuite) testSaveSubscribedData(url *common.URL) { subscribeMi := newSubscribeMetadataIdentifier("provider") urls := []string{url.String()} bytes, _ := json.Marshal(urls) @@ -152,10 +152,10 @@ func test1(t *testing.T) { suite := newConsulMetadataReportTestSuite(t, m) suite.testStoreProviderMetadata() suite.testStoreConsumerMetadata() - suite.testSaveServiceMetadata(*url) + suite.testSaveServiceMetadata(url) suite.testGetExportedURLs() suite.testRemoveServiceMetadata() - suite.testSaveSubscribedData(*url) + suite.testSaveSubscribedData(url) suite.testGetSubscribedURLs() suite.testGetServiceDefinition() } diff --git a/metadata/report/delegate/delegate_report.go b/metadata/report/delegate/delegate_report.go index cdd29ab2e483647ed90f37032e10d174f7583e39..836a8f9ef4aac23558e44c1c9c5adedd585d6da4 100644 --- a/metadata/report/delegate/delegate_report.go +++ b/metadata/report/delegate/delegate_report.go @@ -94,7 +94,7 @@ func (mrr *metadataReportRetry) startRetryTask() { // MetadataReport is a absolute delegate for MetadataReport type MetadataReport struct { - reportUrl common.URL + reportUrl *common.URL syncReport bool metadataReportRetry *metadataReportRetry @@ -215,7 +215,7 @@ func (mr *MetadataReport) StoreConsumerMetadata(identifier *identifier.MetadataI } // SaveServiceMetadata will delegate to call remote metadata's sdk to save service metadata -func (mr *MetadataReport) SaveServiceMetadata(identifier *identifier.ServiceMetadataIdentifier, url common.URL) error { +func (mr *MetadataReport) SaveServiceMetadata(identifier *identifier.ServiceMetadataIdentifier, url *common.URL) error { report := instance.GetMetadataReportInstance() if mr.syncReport { return report.SaveServiceMetadata(identifier, url) @@ -241,7 +241,7 @@ func (mr *MetadataReport) GetExportedURLs(identifier *identifier.ServiceMetadata } // SaveSubscribedData will delegate to call remote metadata's sdk to save subscribed data -func (mr *MetadataReport) SaveSubscribedData(identifier *identifier.SubscriberMetadataIdentifier, urls []common.URL) error { +func (mr *MetadataReport) SaveSubscribedData(identifier *identifier.SubscriberMetadataIdentifier, urls []*common.URL) error { urlStrList := make([]string, 0, len(urls)) for _, url := range urls { urlStrList = append(urlStrList, url.String()) @@ -260,13 +260,13 @@ func (mr *MetadataReport) SaveSubscribedData(identifier *identifier.SubscriberMe } // GetSubscribedURLs will delegate to call remote metadata's sdk to get subscribed urls -func (MetadataReport) GetSubscribedURLs(identifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { +func (mr *MetadataReport) GetSubscribedURLs(identifier *identifier.SubscriberMetadataIdentifier) ([]string, error) { report := instance.GetMetadataReportInstance() return report.GetSubscribedURLs(identifier) } // GetServiceDefinition will delegate to call remote metadata's sdk to get service definitions -func (MetadataReport) GetServiceDefinition(identifier *identifier.MetadataIdentifier) (string, error) { +func (mr *MetadataReport) GetServiceDefinition(identifier *identifier.MetadataIdentifier) (string, error) { report := instance.GetMetadataReportInstance() return report.GetServiceDefinition(identifier) } diff --git a/metadata/report/delegate/delegate_report_test.go b/metadata/report/delegate/delegate_report_test.go index 3dfca577ba06598b90c553048777951c8823b256..9c30ed9ffdb8424be38d273fd0a78f3351d6e0d5 100644 --- a/metadata/report/delegate/delegate_report_test.go +++ b/metadata/report/delegate/delegate_report_test.go @@ -116,8 +116,8 @@ func getMockDefinition(id *identifier.MetadataIdentifier, t *testing.T) *definit "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{}) + _, err = common.ServiceMap.Register(id.ServiceInterface, protocol, id.Group, id.Version, &definition.UserProvider{}) assert.NoError(t, err) - service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) + service := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey()) return definition.BuildServiceDefinition(*service, url) } diff --git a/metadata/report/etcd/report.go b/metadata/report/etcd/report.go index 097835c986962850d137255ec807b417de1484ad..1939b911822b6b5db3d41f999d4dcb4ca3fae1f2 100644 --- a/metadata/report/etcd/report.go +++ b/metadata/report/etcd/report.go @@ -63,7 +63,7 @@ func (e *etcdMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier *i // 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 { +func (e *etcdMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url *common.URL) error { key := e.getNodeKey(metadataIdentifier) return e.client.Create(key, url.String()) } diff --git a/metadata/report/etcd/report_test.go b/metadata/report/etcd/report_test.go index 5dd8780b48a741a8eb8735b059bc3617a100f63d..28c04869822b166f99d98e91ec6ec3b7fe6626cc 100644 --- a/metadata/report/etcd/report_test.go +++ b/metadata/report/etcd/report_test.go @@ -60,7 +60,7 @@ func TestEtcdMetadataReportFactory_CreateMetadataReport(t *testing.T) { t.Fatal(err) } metadataReportFactory := &etcdMetadataReportFactory{} - metadataReport := metadataReportFactory.CreateMetadataReport(&url) + metadataReport := metadataReportFactory.CreateMetadataReport(url) assert.NotNil(t, metadataReport) e.Close() } @@ -72,7 +72,7 @@ func TestEtcdMetadataReport_CRUD(t *testing.T) { t.Fatal(err) } metadataReportFactory := &etcdMetadataReportFactory{} - metadataReport := metadataReportFactory.CreateMetadataReport(&url) + metadataReport := metadataReportFactory.CreateMetadataReport(url) assert.NotNil(t, metadataReport) err = metadataReport.StoreConsumerMetadata(newMetadataIdentifier("consumer"), "consumer metadata") diff --git a/metadata/report/nacos/report.go b/metadata/report/nacos/report.go index d69913bd8fbb04da2d50770c1196917cb1efdaa5..42e9859c9fae70cc110dc45fca43dd95a5627d6e 100644 --- a/metadata/report/nacos/report.go +++ b/metadata/report/nacos/report.go @@ -69,7 +69,7 @@ func (n *nacosMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifier * } // SaveServiceMetadata saves the metadata. -func (n *nacosMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error { +func (n *nacosMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url *common.URL) error { return n.storeMetadata(vo.ConfigParam{ DataId: metadataIdentifier.GetIdentifierKey(), Group: metadataIdentifier.Group, diff --git a/metadata/report/nacos/report_test.go b/metadata/report/nacos/report_test.go index be01eb22f7e95966c3bf816fdf648629b64380a3..1b7075ec78e4ac496983564b684d8776e1476b63 100644 --- a/metadata/report/nacos/report_test.go +++ b/metadata/report/nacos/report_test.go @@ -19,8 +19,10 @@ package nacos import ( "encoding/json" + "net/http" "strconv" "testing" + "time" ) import ( @@ -36,6 +38,9 @@ import ( ) func TestNacosMetadataReport_CRUD(t *testing.T) { + if !checkNacosServerAlive() { + return + } rpt := newTestReport() assert.NotNil(t, rpt) @@ -111,6 +116,14 @@ func TestNacosMetadataReportFactory_CreateMetadataReport(t *testing.T) { 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) + res := extension.GetMetadataReportFactory("nacos").CreateMetadataReport(regurl) return res } + +func checkNacosServerAlive() bool { + c := http.Client{Timeout: time.Second} + if _, err := c.Get("http://console.nacos.io/nacos/"); err != nil { + return false + } + return true +} diff --git a/metadata/report/report.go b/metadata/report/report.go index 62a9055e843297bd0d69ad94cb09ece64efda85f..dcb414209c1e6188dc195e9609f60124adaf2173 100644 --- a/metadata/report/report.go +++ b/metadata/report/report.go @@ -38,7 +38,7 @@ type MetadataReport interface { // SaveServiceMetadata saves the metadata. // Metadata includes the basic info of the server, // service info, and other user custom info. - SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error + SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, *common.URL) error // RemoveServiceMetadata removes the metadata. RemoveServiceMetadata(*identifier.ServiceMetadataIdentifier) error diff --git a/metadata/report/zookeeper/report.go b/metadata/report/zookeeper/report.go index 8f46bb023054ec27ca0dbff2c249dc0135af07cd..5d5e74007b9901276d7144bf0b052018e2f7e0a7 100644 --- a/metadata/report/zookeeper/report.go +++ b/metadata/report/zookeeper/report.go @@ -63,7 +63,7 @@ func (m *zookeeperMetadataReport) StoreConsumerMetadata(consumerMetadataIdentifi } // SaveServiceMetadata saves the metadata. -func (m *zookeeperMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url common.URL) error { +func (m *zookeeperMetadataReport) SaveServiceMetadata(metadataIdentifier *identifier.ServiceMetadataIdentifier, url *common.URL) error { k := m.rootDir + metadataIdentifier.GetFilePathKey() return m.client.CreateWithValue(k, []byte(url.String())) } diff --git a/metadata/report/zookeeper/report_test.go b/metadata/report/zookeeper/report_test.go index a1e46e2e8d019c0415699ee409833b392a85b504..adedaaa6f4bd71511385085118f55538a5deb091 100644 --- a/metadata/report/zookeeper/report_test.go +++ b/metadata/report/zookeeper/report_test.go @@ -100,7 +100,7 @@ func (suite *zookeeperMetadataReportTestSuite) testStoreConsumerMetadata() { assert.NoError(suite.t, err) } -func (suite *zookeeperMetadataReportTestSuite) testSaveServiceMetadata(url common.URL) { +func (suite *zookeeperMetadataReportTestSuite) testSaveServiceMetadata(url *common.URL) { serviceMi := newServiceMetadataIdentifier("provider") err := suite.m.SaveServiceMetadata(serviceMi, url) assert.NoError(suite.t, err) @@ -119,7 +119,7 @@ func (suite *zookeeperMetadataReportTestSuite) testGetExportedURLs() { assert.NoError(suite.t, err) } -func (suite *zookeeperMetadataReportTestSuite) testSaveSubscribedData(url common.URL) { +func (suite *zookeeperMetadataReportTestSuite) testSaveSubscribedData(url *common.URL) { subscribeMi := newSubscribeMetadataIdentifier("provider") urls := []string{url.String()} bytes, _ := json.Marshal(urls) @@ -153,10 +153,10 @@ func test1(t *testing.T) { suite := newZookeeperMetadataReportTestSuite(t, m) suite.testStoreProviderMetadata() suite.testStoreConsumerMetadata() - suite.testSaveServiceMetadata(*url) + suite.testSaveServiceMetadata(url) suite.testGetExportedURLs() suite.testRemoveServiceMetadata() - suite.testSaveSubscribedData(*url) + suite.testSaveSubscribedData(url) suite.testGetSubscribedURLs() suite.testGetServiceDefinition() } diff --git a/metadata/service/exporter/configurable/exporter.go b/metadata/service/exporter/configurable/exporter.go index f8b4b0c0174cb0e5a8753b814f89ed4d332e2fbe..75e52d8d1b833f95d7f6f09a778bd916bd4afee9 100644 --- a/metadata/service/exporter/configurable/exporter.go +++ b/metadata/service/exporter/configurable/exporter.go @@ -19,6 +19,7 @@ package configurable import ( "context" + "errors" "sync" ) @@ -46,13 +47,18 @@ func NewMetadataServiceExporter(metadataService service.MetadataService) exporte } // Export will export the metadataService -func (exporter *MetadataServiceExporter) Export() error { +func (exporter *MetadataServiceExporter) Export(url *common.URL) error { if !exporter.IsExported() { - serviceConfig := config.NewServiceConfig(constant.SIMPLE_METADATA_SERVICE_NAME, context.Background()) serviceConfig.Protocol = constant.DEFAULT_PROTOCOL + if url == nil || url.SubURL == nil { + return errors.New("metadata server url is nil, pls check your configuration") + } serviceConfig.Protocols = map[string]*config.ProtocolConfig{ - constant.DEFAULT_PROTOCOL: generateMetadataProtocol(), + constant.DEFAULT_PROTOCOL: { + Name: url.SubURL.Protocol, + Port: url.SubURL.Port, + }, } serviceConfig.InterfaceName = constant.METADATA_SERVICE_NAME // identify this is a golang server @@ -96,11 +102,3 @@ func (exporter *MetadataServiceExporter) IsExported() bool { 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 index 9fdbd76757815af0aa975ec6e5f1b20fa5f1a83e..7c2baa2728b4c4888d9dbb117816648d72874c0b 100644 --- a/metadata/service/exporter/configurable/exporter_test.go +++ b/metadata/service/exporter/configurable/exporter_test.go @@ -26,19 +26,20 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" _ "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" + "github.com/apache/dubbo-go/remoting/getty" ) func TestConfigurableExporter(t *testing.T) { - dubbo.SetServerConfig(dubbo.ServerConfig{ + getty.SetServerConfig(getty.ServerConfig{ SessionNumber: 700, SessionTimeout: "20s", - GettySessionParam: dubbo.GettySessionParam{ + GettySessionParam: getty.GettySessionParam{ CompressEncoding: false, TcpNoDelay: true, TcpKeepAlive: true, @@ -55,12 +56,23 @@ func TestConfigurableExporter(t *testing.T) { 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()) + + t.Run("configurableExporterUrlNil", func(t *testing.T) { + assert.Equal(t, false, exported.IsExported()) + assert.Error(t, exported.Export(nil), "metadata server url is nil, pls check your configuration") + }) + + t.Run("configurableExporter", func(t *testing.T) { + registryURL, _ := common.NewURL("service-discovery://localhost:12345") + subURL, _ := common.NewURL("dubbo://localhost:20003") + registryURL.SubURL = subURL + assert.Equal(t, false, exported.IsExported()) + assert.NoError(t, exported.Export(registryURL)) + assert.Equal(t, true, exported.IsExported()) + assert.Regexp(t, "dubbo://:20003/org.apache.dubbo.metadata.MetadataService*", exported.GetExportedURLs()[0].String()) + exported.Unexport() + assert.Equal(t, false, exported.IsExported()) + }) } // mockInitProviderWithSingleRegistry will init a mocked providerConfig diff --git a/metadata/service/exporter/exporter.go b/metadata/service/exporter/exporter.go index cfdef3a0e79d29ce31717c0fc3c575e9e4ba1759..33ceaca467220d1c0e39225abb006485e026f961 100644 --- a/metadata/service/exporter/exporter.go +++ b/metadata/service/exporter/exporter.go @@ -23,7 +23,7 @@ import ( // MetadataServiceExporter will export & unexport the metadata service, get exported url, and return is exported or not type MetadataServiceExporter interface { - Export() error + Export(url *common.URL) 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 index 1f8eeaa55f4a0240746508fee2ff088e3a653ca5..becd804f6741e27dda418a4ac9ce976755c634c4 100644 --- a/metadata/service/inmemory/metadata_service_proxy_factory.go +++ b/metadata/service/inmemory/metadata_service_proxy_factory.go @@ -50,7 +50,7 @@ func createProxy(ins registry.ServiceInstance) service.MetadataService { u := urls[0] p := extension.GetProtocol(u.Protocol) - invoker := p.Refer(*u) + invoker := p.Refer(u) return &MetadataServiceProxy{ invkr: invoker, } diff --git a/metadata/service/inmemory/metadata_service_proxy_factory_test.go b/metadata/service/inmemory/metadata_service_proxy_factory_test.go index 96020e1eb762442f946ccf8b368d6ebe9429d05e..f5e519cd5d2f83b85eaccf636e6771c18c74c277 100644 --- a/metadata/service/inmemory/metadata_service_proxy_factory_test.go +++ b/metadata/service/inmemory/metadata_service_proxy_factory_test.go @@ -66,11 +66,11 @@ func TestCreateProxy(t *testing.T) { type mockProtocol struct { } -func (m mockProtocol) Export(invoker protocol.Invoker) protocol.Exporter { +func (m mockProtocol) Export(protocol.Invoker) protocol.Exporter { panic("implement me") } -func (m mockProtocol) Refer(url common.URL) protocol.Invoker { +func (m mockProtocol) Refer(*common.URL) protocol.Invoker { return &mockInvoker{} } @@ -81,7 +81,7 @@ func (m mockProtocol) Destroy() { type mockInvoker struct { } -func (m *mockInvoker) GetUrl() common.URL { +func (m *mockInvoker) GetUrl() *common.URL { panic("implement me") } diff --git a/metadata/service/inmemory/service.go b/metadata/service/inmemory/service.go index 8269e691f1794fd9ac4b6091c157539e39ad7072..8da78c34207c37a7f7e3f475502ac43e7d88b6fd 100644 --- a/metadata/service/inmemory/service.go +++ b/metadata/service/inmemory/service.go @@ -22,7 +22,6 @@ import ( ) import ( - cm "github.com/Workiva/go-datastructures/common" "github.com/Workiva/go-datastructures/slice/skip" ) @@ -75,23 +74,6 @@ func NewMetadataService() (service.MetadataService, error) { 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 ( @@ -101,7 +83,7 @@ func (mts *MetadataService) addURL(targetMap *sync.Map, url *common.URL) 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)) + wantedUrl := urlSet.(*skip.SkipList).Get(url) if len(wantedUrl) > 0 && wantedUrl[0] != nil { mts.lock.RUnlock() return false @@ -110,12 +92,12 @@ func (mts *MetadataService) addURL(targetMap *sync.Map, url *common.URL) bool { } mts.lock.Lock() // double chk - wantedUrl := urlSet.(*skip.SkipList).Get(Comparator(*url)) + wantedUrl := urlSet.(*skip.SkipList).Get(url) if len(wantedUrl) > 0 && wantedUrl[0] != nil { mts.lock.Unlock() return false } - urlSet.(*skip.SkipList).Insert(Comparator(*url)) + urlSet.(*skip.SkipList).Insert(url) mts.lock.Unlock() return true } @@ -124,7 +106,7 @@ func (mts *MetadataService) addURL(targetMap *sync.Map, url *common.URL) bool { 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)) + value.(*skip.SkipList).Delete(url) mts.lock.Unlock() mts.lock.RLock() defer mts.lock.RUnlock() @@ -135,14 +117,14 @@ func (mts *MetadataService) removeURL(targetMap *sync.Map, url *common.URL) { } // getAllService can return all the exportedUrlString except for metadataService -func (mts *MetadataService) getAllService(services *sync.Map) []common.URL { +func (mts *MetadataService) getAllService(services *sync.Map) []*common.URL { // using skip list to dedup and sorting - res := make([]common.URL, 0) + var res []*common.URL 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 { + url := urls.ByPosition(i).(*common.URL) + if url.Service() != constant.METADATA_SERVICE_NAME { res = append(res, url) } } @@ -153,13 +135,13 @@ func (mts *MetadataService) getAllService(services *sync.Map) []common.URL { } // 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) +func (mts *MetadataService) getSpecifiedService(services *sync.Map, serviceKey string, protocol string) []*common.URL { + var res []*common.URL 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)) + url := urls.ByPosition(i).(*common.URL) if len(protocol) == 0 || protocol == constant.ANY_VALUE || url.Protocol == protocol || url.GetParam(constant.PROTOCOL_KEY, "") == protocol { res = append(res, url) } @@ -170,34 +152,34 @@ func (mts *MetadataService) getSpecifiedService(services *sync.Map, serviceKey s } // ExportURL can store the in memory -func (mts *MetadataService) ExportURL(url common.URL) (bool, error) { - return mts.addURL(mts.exportedServiceURLs, &url), nil +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) +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 +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) +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 { +func (mts *MetadataService) PublishServiceDefinition(url *common.URL) error { interfaceName := url.GetParam(constant.INTERFACE_KEY, "") isGeneric := url.GetParamBool(constant.GENERIC_KEY, false) if len(interfaceName) > 0 && !isGeneric { - service := common.ServiceMap.GetService(url.Protocol, url.GetParam(constant.BEAN_NAME_KEY, url.Service())) - sd := definition.BuildServiceDefinition(*service, url) + tmpService := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey()) + sd := definition.BuildServiceDefinition(*tmpService, url) data, err := sd.ToBytes() if err != nil { logger.Errorf("publishProvider getServiceDescriptor error. providerUrl:%v , error:%v ", url, err) @@ -221,7 +203,7 @@ func (mts *MetadataService) GetExportedURLs(serviceInterface string, group strin } // GetSubscribedURLs get all subscribedUrl -func (mts *MetadataService) GetSubscribedURLs() ([]common.URL, error) { +func (mts *MetadataService) GetSubscribedURLs() ([]*common.URL, error) { return mts.getAllService(mts.subscribedServiceURLs), nil } @@ -239,7 +221,7 @@ func (mts *MetadataService) GetServiceDefinitionByServiceKey(serviceKey string) } // RefreshMetadata will always return true because it will be implement by remote service -func (mts *MetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { +func (mts *MetadataService) RefreshMetadata(string, string) (bool, error) { return true, nil } diff --git a/metadata/service/inmemory/service_proxy.go b/metadata/service/inmemory/service_proxy.go index 7e01439f042a2046559188ec9df6924da0236cb1..7e14293a6766492c1c1b02eef9429d1adeb539ae 100644 --- a/metadata/service/inmemory/service_proxy.go +++ b/metadata/service/inmemory/service_proxy.go @@ -55,7 +55,7 @@ func (m *MetadataServiceProxy) GetExportedURLs(serviceInterface string, group st 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.WithAttachments(map[string]interface{}{constant.ASYNC_KEY: "false"}), invocation.WithParameterValues([]reflect.Value{siV, gV, vV, pV})) res := m.invkr.Invoke(context.Background(), inv) @@ -88,34 +88,34 @@ func (m *MetadataServiceProxy) ServiceName() (string, error) { return "", nil } -func (m *MetadataServiceProxy) ExportURL(url common.URL) (bool, error) { +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 { +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) { +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 { +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 { +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) { +func (m *MetadataServiceProxy) GetSubscribedURLs() ([]*common.URL, error) { logger.Error("you should never invoke this implementation") - return []common.URL{}, nil + return nil, nil } func (m *MetadataServiceProxy) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { diff --git a/metadata/service/inmemory/service_proxy_test.go b/metadata/service/inmemory/service_proxy_test.go index 0d75517e418133ffbf3804ec96f061dda09b9e5e..f7fc8fd88e5f31804253dc996cb99b241a519d14 100644 --- a/metadata/service/inmemory/service_proxy_test.go +++ b/metadata/service/inmemory/service_proxy_test.go @@ -49,16 +49,16 @@ func TestMetadataServiceProxy_GetExportedURLs(t *testing.T) { func TestNewMetadataService(t *testing.T) { pxy := createPxy() pxy.ServiceName() - pxy.PublishServiceDefinition(common.URL{}) + pxy.PublishServiceDefinition(&common.URL{}) pxy.GetServiceDefinition(constant.ANY_VALUE, constant.ANY_VALUE, constant.ANY_VALUE) pxy.Version() pxy.GetSubscribedURLs() - pxy.UnsubscribeURL(common.URL{}) + pxy.UnsubscribeURL(&common.URL{}) pxy.GetServiceDefinitionByServiceKey("any") - pxy.ExportURL(common.URL{}) - pxy.SubscribeURL(common.URL{}) + pxy.ExportURL(&common.URL{}) + pxy.SubscribeURL(&common.URL{}) pxy.MethodMapper() - pxy.UnexportURL(common.URL{}) + pxy.UnexportURL(&common.URL{}) pxy.RefreshMetadata(constant.ANY_VALUE, constant.ANY_VALUE) } diff --git a/metadata/service/inmemory/service_test.go b/metadata/service/inmemory/service_test.go index 048c286fdf28fba6a15a86164df0789d421f0797..256412c2917d39c0ff15db11121718ba983cd7ab 100644 --- a/metadata/service/inmemory/service_test.go +++ b/metadata/service/inmemory/service_test.go @@ -82,7 +82,7 @@ func TestMetadataService(t *testing.T) { assert.Equal(t, 0, len(list4)) userProvider := &definition.UserProvider{} - common.ServiceMap.Register(serviceName, protocol, userProvider) + common.ServiceMap.Register(serviceName, protocol, group, version, userProvider) mts.PublishServiceDefinition(u) expected := "{\"CanonicalName\":\"com.ikurento.user.UserProvider\",\"CodeSource\":\"\"," + "\"Methods\":[{\"Name\":\"GetUser\",\"ParameterTypes\":[\"slice\"],\"ReturnType\":\"ptr\"," + diff --git a/metadata/service/remote/service.go b/metadata/service/remote/service.go index ae83a69bef0af1614352c99c1e512a63770a0eff..efd16bdc6dad555f5a99bc4b2a02ee2a08202537 100644 --- a/metadata/service/remote/service.go +++ b/metadata/service/remote/service.go @@ -89,33 +89,33 @@ func (mts *MetadataService) setInMemoryMetadataService(metadata *inmemory.Metada } // ExportURL will be implemented by in memory service -func (mts *MetadataService) ExportURL(url common.URL) (bool, error) { +func (mts *MetadataService) ExportURL(url *common.URL) (bool, error) { return mts.inMemoryMetadataService.ExportURL(url) } // UnexportURL remove @url's metadata -func (mts *MetadataService) UnexportURL(url common.URL) error { +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) { +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 { +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 { +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())) + sv := common.ServiceMap.GetServiceByServiceKey(url.Protocol, url.ServiceKey()) sd := definition.BuildServiceDefinition(*sv, url) id := &identifier.MetadataIdentifier{ BaseMetadataIdentifier: identifier.BaseMetadataIdentifier{ @@ -139,7 +139,7 @@ func (mts *MetadataService) GetExportedURLs(serviceInterface string, group strin } // GetSubscribedURLs will be implemented by in memory service -func (mts *MetadataService) GetSubscribedURLs() ([]common.URL, error) { +func (mts *MetadataService) GetSubscribedURLs() ([]*common.URL, error) { return mts.inMemoryMetadataService.GetSubscribedURLs() } diff --git a/metadata/service/remote/service_proxy.go b/metadata/service/remote/service_proxy.go index eaf7a02f4a0f3a8280835940bd8da720a0bde9f5..3199aa6dfff6f5e8b6036c44f452d16480b0380c 100644 --- a/metadata/service/remote/service_proxy.go +++ b/metadata/service/remote/service_proxy.go @@ -45,27 +45,27 @@ func (m *metadataServiceProxy) ServiceName() (string, error) { return m.serviceName, nil } -func (m *metadataServiceProxy) ExportURL(url common.URL) (bool, error) { +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 { +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) { +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 { +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 { +func (m *metadataServiceProxy) PublishServiceDefinition(url *common.URL) error { logger.Error("you should never invoke this implementation") return nil } @@ -85,7 +85,7 @@ func (m *metadataServiceProxy) GetExportedURLs(serviceInterface string, group st if err != nil { return []interface{}{}, nil } - res := make([]common.URL, 0, len(urls)) + var res []*common.URL for _, s := range urls { u, err := common.NewURL(s) if err != nil { @@ -101,9 +101,9 @@ func (m *metadataServiceProxy) MethodMapper() map[string]string { return map[string]string{} } -func (m *metadataServiceProxy) GetSubscribedURLs() ([]common.URL, error) { +func (m *metadataServiceProxy) GetSubscribedURLs() ([]*common.URL, error) { logger.Error("you should never invoke this implementation") - return []common.URL{}, nil + return nil, nil } func (m *metadataServiceProxy) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { diff --git a/metadata/service/remote/service_proxy_test.go b/metadata/service/remote/service_proxy_test.go index c284bb22123e731f3b8905f70508856bc767ace6..8bccbb8bbb70d1533fa3dad78bab59e82ff2e5b3 100644 --- a/metadata/service/remote/service_proxy_test.go +++ b/metadata/service/remote/service_proxy_test.go @@ -55,15 +55,15 @@ func TestMetadataServiceProxy_GetServiceDefinition(t *testing.T) { func TestMetadataServiceProxy(t *testing.T) { pxy := createProxy() pxy.ServiceName() - pxy.PublishServiceDefinition(common.URL{}) + pxy.PublishServiceDefinition(&common.URL{}) pxy.Version() pxy.GetSubscribedURLs() - pxy.UnsubscribeURL(common.URL{}) + pxy.UnsubscribeURL(&common.URL{}) pxy.GetServiceDefinitionByServiceKey("any") - pxy.ExportURL(common.URL{}) - pxy.SubscribeURL(common.URL{}) + pxy.ExportURL(&common.URL{}) + pxy.SubscribeURL(&common.URL{}) pxy.MethodMapper() - pxy.UnexportURL(common.URL{}) + pxy.UnexportURL(&common.URL{}) pxy.Reference() pxy.RefreshMetadata(constant.ANY_VALUE, constant.ANY_VALUE) } @@ -89,7 +89,7 @@ func prepareTest() { return &mockMetadataReportFactory{} }) u, _ := common.NewURL("mock://localhost") - instance.GetMetadataReportInstance(&u) + instance.GetMetadataReportInstance(u) } type mockMetadataReportFactory struct { @@ -110,7 +110,7 @@ func (m mockMetadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier panic("implement me") } -func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, common.URL) error { +func (m mockMetadataReport) SaveServiceMetadata(*identifier.ServiceMetadataIdentifier, *common.URL) error { return nil } diff --git a/metadata/service/remote/service_test.go b/metadata/service/remote/service_test.go index 734f0989ab06ef17caeef241cd067c678fb8b2ad..71586cc1dcc87dc8644a25c9a89842bd84fe9f0a 100644 --- a/metadata/service/remote/service_test.go +++ b/metadata/service/remote/service_test.go @@ -39,7 +39,7 @@ import ( ) var ( - serviceMetadata = make(map[*identifier.ServiceMetadataIdentifier]common.URL, 4) + serviceMetadata = make(map[*identifier.ServiceMetadataIdentifier]*common.URL, 4) subscribedMetadata = make(map[*identifier.SubscriberMetadataIdentifier]string, 4) ) @@ -65,7 +65,7 @@ func (metadataReport) StoreConsumerMetadata(*identifier.MetadataIdentifier, stri return nil } -func (mr *metadataReport) SaveServiceMetadata(id *identifier.ServiceMetadataIdentifier, url common.URL) error { +func (mr *metadataReport) SaveServiceMetadata(id *identifier.ServiceMetadataIdentifier, url *common.URL) error { logger.Infof("SaveServiceMetadata , url is %v", url) serviceMetadata[id] = url return nil @@ -97,7 +97,7 @@ 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) + instance.GetMetadataReportInstance(u) mts, err := newMetadataService() assert.NoError(t, err) mts.(*MetadataService).setInMemoryMetadataService(mockInmemoryProc(t)) @@ -126,7 +126,7 @@ func mockInmemoryProc(t *testing.T) *inmemory.MetadataService { _, err = mts.SubscribeURL(u) assert.NoError(t, err) - _, err = common.ServiceMap.Register(serviceName, protocol, userProvider) + _, err = common.ServiceMap.Register(serviceName, protocol, group, version, userProvider) assert.NoError(t, err) err = mts.PublishServiceDefinition(u) assert.NoError(t, err) diff --git a/metadata/service/service.go b/metadata/service/service.go index f6509d0a72eb26e488dfb4fdeef5f4bbfd6b1bea..1d90f8a516831adcae20163a3620dd765459310d 100644 --- a/metadata/service/service.go +++ b/metadata/service/service.go @@ -34,15 +34,15 @@ type MetadataService interface { // 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) + ExportURL(url *common.URL) (bool, error) // UnexportURL will delete the exported url in metadata - UnexportURL(url common.URL) error + UnexportURL(url *common.URL) error // SubscribeURL will store the subscribed url in metadata - SubscribeURL(url common.URL) (bool, error) + SubscribeURL(url *common.URL) (bool, error) // UnsubscribeURL will delete the subscribed url in metadata - UnsubscribeURL(url common.URL) error + UnsubscribeURL(url *common.URL) error // PublishServiceDefinition will generate the target url's code info - PublishServiceDefinition(url common.URL) error + 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{} @@ -53,7 +53,7 @@ type MetadataService interface { // GetExportedURLs will get the target subscribed url in metadata // the url should be unique - GetSubscribedURLs() ([]common.URL, error) + 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 @@ -122,7 +122,7 @@ func getExportedServicesRevision(serviceInstance registry.ServiceInstance) strin return metaData[constant.EXPORTED_SERVICES_REVISION_PROPERTY_NAME] } -func ConvertURLArrToIntfArr(urls []common.URL) []interface{} { +func ConvertURLArrToIntfArr(urls []*common.URL) []interface{} { if len(urls) == 0 { return []interface{}{} } diff --git a/metrics/prometheus/reporter.go b/metrics/prometheus/reporter.go index bd1e7986ca709a4e10dfcad04d2380931308d568..266c8206c5ea5b6dc07402943bcebdeb6dd7dea7 100644 --- a/metrics/prometheus/reporter.go +++ b/metrics/prometheus/reporter.go @@ -24,6 +24,7 @@ import ( "sync" "time" ) + import ( "github.com/prometheus/client_golang/prometheus" ) @@ -64,7 +65,6 @@ var ( // should initialize after loading configuration func init() { - extension.SetMetricReporter(reporterName, newPrometheusReporter) } @@ -72,12 +72,10 @@ func init() { // if you want to use this feature, you need to initialize your prometheus. // https://prometheus.io/docs/guides/go-application/ type PrometheusReporter struct { - // report the consumer-side's summary data consumerSummaryVec *prometheus.SummaryVec // report the provider-side's summary data providerSummaryVec *prometheus.SummaryVec - // report the provider-side's histogram data providerHistogramVec *prometheus.HistogramVec // report the consumer-side's histogram data @@ -130,13 +128,13 @@ func newHistogramVec(side string) *prometheus.HistogramVec { } // whether this url represents the application received the request as server -func isProvider(url common.URL) bool { +func isProvider(url *common.URL) bool { role := url.GetParam(constant.ROLE_KEY, "") return strings.EqualFold(role, strconv.Itoa(common.PROVIDER)) } // whether this url represents the application sent then request as client -func isConsumer(url common.URL) bool { +func isConsumer(url *common.URL) bool { role := url.GetParam(constant.ROLE_KEY, "") return strings.EqualFold(role, strconv.Itoa(common.CONSUMER)) } diff --git a/metrics/prometheus/reporter_test.go b/metrics/prometheus/reporter_test.go index 0cb7d09a2c8e71fb88b54789c8eb3ee2cf967fbf..1b9853f4d20739269861aac680b1ec491ef552d6 100644 --- a/metrics/prometheus/reporter_test.go +++ b/metrics/prometheus/reporter_test.go @@ -26,6 +26,7 @@ import ( import ( "github.com/stretchr/testify/assert" ) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/extension" @@ -43,7 +44,7 @@ func TestPrometheusReporter_Report(t *testing.T) { "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") invoker := protocol.NewBaseInvoker(url) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) assert.False(t, isConsumer(url)) diff --git a/metrics/reporter.go b/metrics/reporter.go index 9a7094fa62d9c0fa3e6ee0a8ef373f91c28d2c90..24d75aa4fe949737d7ca94cfd339a32af14a40be 100644 --- a/metrics/reporter.go +++ b/metrics/reporter.go @@ -21,6 +21,7 @@ import ( "context" "time" ) + import ( "github.com/apache/dubbo-go/protocol" ) diff --git a/protocol/dubbo/client.go b/protocol/dubbo/client.go deleted file mode 100644 index 530beba3512ec09e353b632b1a4b75d75f8a5ae2..0000000000000000000000000000000000000000 --- a/protocol/dubbo/client.go +++ /dev/null @@ -1,364 +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 dubbo - -import ( - "math/rand" - "strings" - "sync" - "time" -) - -import ( - "github.com/apache/dubbo-getty" - hessian "github.com/apache/dubbo-go-hessian2" - gxsync "github.com/dubbogo/gost/sync" - perrors "github.com/pkg/errors" - "go.uber.org/atomic" - "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" -) - -var ( - errInvalidCodecType = perrors.New("illegal CodecType") - errInvalidAddress = perrors.New("remote address invalid or empty") - errSessionNotExist = perrors.New("session not exist") - errClientClosed = perrors.New("client closed") - errClientReadTimeout = perrors.New("client read timeout") - - clientConf *ClientConfig - clientGrpool *gxsync.TaskPool -) - -func init() { - - // load clientconfig from consumer_config - // default use dubbo - consumerConfig := config.GetConsumerConfig() - if consumerConfig.ApplicationConfig == nil { - return - } - protocolConf := config.GetConsumerConfig().ProtocolConf - defaultClientConfig := GetDefaultClientConfig() - if protocolConf == nil { - logger.Info("protocol_conf default use dubbo config") - } else { - dubboConf := protocolConf.(map[interface{}]interface{})[DUBBO] - if dubboConf == nil { - logger.Warnf("dubboConf is nil") - return - } - dubboConfByte, err := yaml.Marshal(dubboConf) - if err != nil { - panic(err) - } - err = yaml.Unmarshal(dubboConfByte, &defaultClientConfig) - if err != nil { - panic(err) - } - } - clientConf = &defaultClientConfig - if err := clientConf.CheckValidity(); err != nil { - logger.Warnf("[CheckValidity] error: %v", err) - return - } - setClientGrpool() - - rand.Seed(time.Now().UnixNano()) -} - -// SetClientConf set dubbo client config. -func SetClientConf(c ClientConfig) { - clientConf = &c - err := clientConf.CheckValidity() - if err != nil { - logger.Warnf("[ClientConfig CheckValidity] error: %v", err) - return - } - setClientGrpool() -} - -// GetClientConf get dubbo client config. -func GetClientConf() ClientConfig { - return *clientConf -} - -func setClientGrpool() { - if clientConf.GrPoolSize > 1 { - clientGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(clientConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(clientConf.QueueLen), - gxsync.WithTaskPoolTaskQueueNumber(clientConf.QueueNumber)) - } -} - -// Options is option for create dubbo client -type Options struct { - // connect timeout - ConnectTimeout time.Duration - // request timeout - RequestTimeout time.Duration -} - -//AsyncCallbackResponse async response for dubbo -type AsyncCallbackResponse struct { - common.CallbackResponse - Opts Options - Cause error - Start time.Time // invoke(call) start time == write start time - ReadStart time.Time // read start time, write duration = ReadStart - Start - Reply interface{} -} - -// Client is dubbo protocol client. -type Client struct { - opts Options - conf ClientConfig - pool *gettyRPCClientPool - sequence atomic.Uint64 - - pendingResponses *sync.Map -} - -// NewClient create a new Client. -func NewClient(opt Options) *Client { - - switch { - case opt.ConnectTimeout == 0: - opt.ConnectTimeout = 3 * time.Second - fallthrough - case opt.RequestTimeout == 0: - opt.RequestTimeout = 3 * time.Second - } - - // make sure that client request sequence is an odd number - initSequence := uint64(rand.Int63n(time.Now().UnixNano())) - if initSequence%2 == 0 { - initSequence++ - } - - c := &Client{ - opts: opt, - pendingResponses: new(sync.Map), - conf: *clientConf, - } - c.sequence.Store(initSequence) - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - return c -} - -// Request is dubbo protocol request. -type Request struct { - addr string - svcUrl common.URL - method string - args interface{} - atta map[string]string -} - -// 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, - svcUrl: svcUrl, - method: method, - args: args, - atta: atta, - } -} - -// Response is dubbo protocol response. -type Response struct { - reply interface{} - atta map[string]string -} - -// NewResponse creates a new Response. -func NewResponse(reply interface{}, atta map[string]string) *Response { - return &Response{ - reply: reply, - atta: atta, - } -} - -// 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 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 - } - - return perrors.WithStack(c.call(ct, request, response, nil)) -} - -// 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, "") - p.Service.Version = request.svcUrl.GetParam(constant.VERSION_KEY, "") - p.Service.Group = request.svcUrl.GetParam(constant.GROUP_KEY, "") - p.Service.Method = request.method - c.pool.sslEnabled = request.svcUrl.GetParamBool(constant.SSL_ENABLED_KEY, false) - - p.Service.Timeout = c.opts.RequestTimeout - var timeout = request.svcUrl.GetParam(strings.Join([]string{constant.METHOD_KEYS, request.method + constant.RETRIES_KEY}, "."), "") - if len(timeout) != 0 { - if t, err := time.ParseDuration(timeout); err == nil { - p.Service.Timeout = t - } - } - - p.Header.SerialID = byte(S_Dubbo) - p.Body = hessian.NewRequest(request.args, request.atta) - - var rsp *PendingResponse - if ct != CT_OneWay { - p.Header.Type = hessian.PackageRequest_TwoWay - rsp = NewPendingResponse() - rsp.response = response - rsp.callback = callback - } else { - p.Header.Type = hessian.PackageRequest - } - - var ( - err error - session getty.Session - conn *gettyRPCClient - ) - conn, session, err = c.selectSession(request.addr) - if err != nil { - return perrors.WithStack(err) - } - if session == nil { - return errSessionNotExist - } - defer func() { - if err == nil { - c.pool.put(conn) - return - } - conn.close() - }() - - if err = c.transfer(session, p, rsp); err != nil { - return perrors.WithStack(err) - } - - if ct == CT_OneWay || callback != nil { - return nil - } - - select { - case <-getty.GetTimeWheel().After(c.opts.RequestTimeout): - c.removePendingResponse(SequenceType(rsp.seq)) - return perrors.WithStack(errClientReadTimeout) - case <-rsp.done: - err = rsp.err - } - - return perrors.WithStack(err) -} - -// Close close the client pool. -func (c *Client) Close() { - if c.pool != nil { - c.pool.close() - } - c.pool = nil -} - -func (c *Client) selectSession(addr string) (*gettyRPCClient, getty.Session, error) { - rpcClient, err := c.pool.getGettyRpcClient(DUBBO, addr) - if err != nil { - return nil, nil, perrors.WithStack(err) - } - return rpcClient, rpcClient.selectSession(), nil -} - -func (c *Client) heartbeat(session getty.Session) error { - return c.transfer(session, nil, NewPendingResponse()) -} - -func (c *Client) transfer(session getty.Session, pkg *DubboPackage, - rsp *PendingResponse) error { - - var ( - sequence uint64 - err error - ) - - sequence = c.sequence.Add(1) - - if pkg == nil { - pkg = &DubboPackage{} - pkg.Body = hessian.NewRequest([]interface{}{}, nil) - pkg.Body = []interface{}{} - pkg.Header.Type = hessian.PackageHeartbeat - pkg.Header.SerialID = byte(S_Dubbo) - } - pkg.Header.ID = int64(sequence) - - // cond1 - if rsp != nil { - rsp.seq = sequence - c.addPendingResponse(rsp) - } - - err = session.WritePkg(pkg, c.opts.RequestTimeout) - if err != nil { - c.removePendingResponse(SequenceType(rsp.seq)) - } else if rsp != nil { // cond2 - // cond2 should not merged with cond1. cause the response package may be returned very - // soon and it will be handled by other goroutine. - rsp.readStart = time.Now() - } - - return perrors.WithStack(err) -} - -func (c *Client) addPendingResponse(pr *PendingResponse) { - c.pendingResponses.Store(SequenceType(pr.seq), pr) -} - -func (c *Client) removePendingResponse(seq SequenceType) *PendingResponse { - if c.pendingResponses == nil { - return nil - } - if presp, ok := c.pendingResponses.Load(seq); ok { - c.pendingResponses.Delete(seq) - return presp.(*PendingResponse) - } - return nil -} diff --git a/protocol/dubbo/client_test.go b/protocol/dubbo/client_test.go deleted file mode 100644 index 8b0ba169b82910652c64011c47568c7a018ae5e0..0000000000000000000000000000000000000000 --- a/protocol/dubbo/client_test.go +++ /dev/null @@ -1,305 +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 dubbo - -import ( - "bytes" - "context" - "sync" - "testing" - "time" -) - -import ( - hessian "github.com/apache/dubbo-go-hessian2" - perrors "github.com/pkg/errors" - "github.com/stretchr/testify/assert" -) - -import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/proxy/proxy_factory" - "github.com/apache/dubbo-go/protocol" -) - -const ( - mockMethodNameGetUser = "GetUser" - mockMethodNameGetBigPkg = "GetBigPkg" - mockAddress = "127.0.0.1:20000" -) - -func TestClientCallOneway(t *testing.T) { - proto, url := InitTest(t) - - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3e9, - RequestTimeout: 6e9, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - err := c.CallOneway(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil)) - assert.NoError(t, err) - - // destroy - proto.Destroy() -} - -func TestClientCall(t *testing.T) { - proto, url := InitTest(t) - - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3e9, - RequestTimeout: 10e9, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - var ( - user *User - err error - ) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, mockMethodNameGetBigPkg, []interface{}{nil}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.NotEqual(t, "", user.Id) - assert.NotEqual(t, "", user.Name) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: "username"}, *user) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, "GetUser0", []interface{}{"1", nil, "username"}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: "username"}, *user) - - err = c.Call(NewRequest(mockAddress, url, "GetUser1", []interface{}{}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - - err = c.Call(NewRequest(mockAddress, url, "GetUser2", []interface{}{}, nil), NewResponse(user, nil)) - assert.EqualError(t, err, "error") - - user2 := []interface{}{} - err = c.Call(NewRequest(mockAddress, url, "GetUser3", []interface{}{}, nil), NewResponse(&user2, nil)) - assert.NoError(t, err) - assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) - - user2 = []interface{}{} - err = c.Call(NewRequest(mockAddress, url, "GetUser4", []interface{}{[]interface{}{"1", "username"}}, nil), NewResponse(&user2, nil)) - assert.NoError(t, err) - assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) - - user3 := map[interface{}]interface{}{} - err = c.Call(NewRequest(mockAddress, url, "GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, nil), NewResponse(&user3, nil)) - assert.NoError(t, err) - assert.NotNil(t, user3) - assert.Equal(t, &User{Id: "1", Name: "username"}, user3["key"]) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, "GetUser6", []interface{}{0}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "", Name: ""}, *user) - - user = &User{} - err = c.Call(NewRequest(mockAddress, url, "GetUser6", []interface{}{1}, nil), NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{Id: "1", Name: ""}, *user) - - // destroy - proto.Destroy() -} - -func TestClientAsyncCall(t *testing.T) { - proto, url := InitTest(t) - - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3e9, - RequestTimeout: 6e9, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) - - user := &User{} - lock := sync.Mutex{} - lock.Lock() - err := c.AsyncCall(NewRequest(mockAddress, url, mockMethodNameGetUser, []interface{}{"1", "username"}, nil), func(response common.CallbackResponse) { - r := response.(AsyncCallbackResponse) - assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) - lock.Unlock() - }, NewResponse(user, nil)) - assert.NoError(t, err) - assert.Equal(t, User{}, *user) - - // destroy - lock.Lock() - proto.Destroy() - lock.Unlock() -} - -func InitTest(t *testing.T) (protocol.Protocol, common.URL) { - - hessian.RegisterPOJO(&User{}) - - 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) - - // config - SetClientConf(ClientConfig{ - ConnectionNum: 2, - HeartbeatPeriod: "5s", - SessionTimeout: "20s", - PoolTTL: 600, - PoolSize: 64, - GettySessionParam: GettySessionParam{ - CompressEncoding: false, - TcpNoDelay: true, - TcpKeepAlive: true, - KeepAlivePeriod: "120s", - TcpRBufSize: 262144, - TcpWBufSize: 65536, - PkgWQSize: 512, - TcpReadTimeout: "4s", - TcpWriteTimeout: "5s", - WaitTimeout: "1s", - MaxMsgLen: 10240000000, - SessionName: "client", - }, - }) - assert.NoError(t, clientConf.CheckValidity()) - SetServerConfig(ServerConfig{ - SessionNumber: 700, - SessionTimeout: "20s", - GettySessionParam: 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", - }}) - assert.NoError(t, srvConf.CheckValidity()) - - // Export - proto := GetProtocol() - url, err := common.NewURL("dubbo://127.0.0.1:20000/UserProvider?anyhost=true&" + - "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + - "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + - "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + - "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") - assert.NoError(t, err) - proto.Export(&proxy_factory.ProxyInvoker{ - BaseInvoker: *protocol.NewBaseInvoker(url), - }) - - time.Sleep(time.Second * 2) - - return proto, url -} - -////////////////////////////////// -// provider -////////////////////////////////// - -type ( - User struct { - Id string `json:"id"` - Name string `json:"name"` - } - - UserProvider struct { - user map[string]User - } -) - -// size:4801228 -func (u *UserProvider) GetBigPkg(ctx context.Context, req []interface{}, rsp *User) error { - argBuf := new(bytes.Buffer) - for i := 0; i < 4000; i++ { - argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") - argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") - } - rsp.Id = argBuf.String() - rsp.Name = argBuf.String() - return nil -} - -func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User) error { - rsp.Id = req[0].(string) - rsp.Name = req[1].(string) - return nil -} - -func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { - return User{Id: id, Name: name}, nil -} - -func (u *UserProvider) GetUser1() error { - return nil -} - -func (u *UserProvider) GetUser2() error { - return perrors.New("error") -} - -func (u *UserProvider) GetUser3(rsp *[]interface{}) error { - *rsp = append(*rsp, User{Id: "1", Name: "username"}) - return nil -} - -func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) ([]interface{}, error) { - - return []interface{}{User{Id: req[0].([]interface{})[0].(string), Name: req[0].([]interface{})[1].(string)}}, nil -} - -func (u *UserProvider) GetUser5(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { - return map[interface{}]interface{}{"key": User{Id: req[0].(map[interface{}]interface{})["id"].(string), Name: req[0].(map[interface{}]interface{})["name"].(string)}}, nil -} - -func (u *UserProvider) GetUser6(id int64) (*User, error) { - if id == 0 { - return nil, nil - } - return &User{Id: "1"}, nil -} - -func (u *UserProvider) Reference() string { - return "UserProvider" -} - -func (u User) JavaClassName() string { - return "com.ikurento.user.User" -} diff --git a/protocol/dubbo/codec.go b/protocol/dubbo/codec.go deleted file mode 100644 index 9781c70115e3d8b9e41c3418ae7b859608651ee8..0000000000000000000000000000000000000000 --- a/protocol/dubbo/codec.go +++ /dev/null @@ -1,157 +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 dubbo - -import ( - "bufio" - "bytes" - "fmt" - "time" -) - -import ( - "github.com/apache/dubbo-go-hessian2" - "github.com/apache/dubbo-go/common" - perrors "github.com/pkg/errors" -) - -//SerialID serial ID -type SerialID byte - -const ( - // S_Dubbo dubbo serial id - S_Dubbo SerialID = 2 -) - -//CallType call type -type CallType int32 - -const ( - // CT_UNKNOWN unknown call type - CT_UNKNOWN CallType = 0 - // CT_OneWay call one way - CT_OneWay CallType = 1 - // CT_TwoWay call in request/response - CT_TwoWay CallType = 2 -) - -//////////////////////////////////////////// -// dubbo package -//////////////////////////////////////////// - -// SequenceType sequence type -type SequenceType int64 - -// nolint -type DubboPackage struct { - Header hessian.DubboHeader - Service hessian.Service - Body interface{} - 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 encode hessian package. -func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { - codec := hessian.NewHessianCodec(nil) - - pkg, err := codec.Write(p.Service, p.Header, p.Body) - if err != nil { - return nil, perrors.WithStack(err) - } - - return bytes.NewBuffer(pkg), nil -} - -// Unmarshal decodes 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() - if bufLen < hessian.HEADER_LENGTH { - return perrors.WithStack(hessian.ErrHeaderNotEnough) - } - - codec := hessian.NewHessianCodec(bufio.NewReaderSize(buf, bufLen)) - - // read header - err := codec.ReadHeader(&p.Header) - if err != nil { - return perrors.WithStack(err) - } - - if len(opts) != 0 { // for client - client, ok := opts[0].(*Client) - if !ok { - return perrors.Errorf("opts[0] is not of type *Client") - } - - if p.Header.Type&hessian.PackageRequest != 0x00 { - // size of this array must be '7' - // https://github.com/apache/dubbo-go-hessian2/blob/master/request.go#L272 - p.Body = make([]interface{}, 7) - } else { - pendingRsp, ok := client.pendingResponses.Load(SequenceType(p.Header.ID)) - if !ok { - return perrors.Errorf("client.GetPendingResponse(%v) = nil", p.Header.ID) - } - p.Body = &hessian.Response{RspObj: pendingRsp.(*PendingResponse).response.reply} - } - } - - // read body - err = codec.ReadBody(p.Body) - return perrors.WithStack(err) -} - -//////////////////////////////////////////// -// PendingResponse -//////////////////////////////////////////// - -// PendingResponse is a pending response. -type PendingResponse struct { - seq uint64 - err error - start time.Time - readStart time.Time - callback common.AsyncCallback - response *Response - done chan struct{} -} - -// NewPendingResponse create a PendingResponses. -func NewPendingResponse() *PendingResponse { - return &PendingResponse{ - start: time.Now(), - response: &Response{}, - done: make(chan struct{}), - } -} - -// GetCallResponse get AsyncCallbackResponse. -func (r PendingResponse) GetCallResponse() common.CallbackResponse { - return AsyncCallbackResponse{ - Cause: r.err, - Start: r.start, - ReadStart: r.readStart, - Reply: r.response, - } -} diff --git a/protocol/dubbo/dubbo_codec.go b/protocol/dubbo/dubbo_codec.go new file mode 100644 index 0000000000000000000000000000000000000000..5e859a7fa2254ba0e4806bc60c037c47777bc641 --- /dev/null +++ b/protocol/dubbo/dubbo_codec.go @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dubbo + +import ( + "bytes" + "strconv" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/dubbo/impl" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +//SerialID serial ID +type SerialID byte + +func init() { + codec := &DubboCodec{} + // this is for registry dubboCodec of dubbo protocol + remoting.RegistryCodec("dubbo", codec) +} + +// DubboCodec. It is implements remoting.Codec +type DubboCodec struct { +} + +// encode request for transport +func (c *DubboCodec) EncodeRequest(request *remoting.Request) (*bytes.Buffer, error) { + if request.Event { + return c.encodeHeartbeartReqeust(request) + } + + invoc, ok := request.Data.(*protocol.Invocation) + if !ok { + err := perrors.Errorf("encode request failed for parameter type :%+v", request) + logger.Errorf(err.Error()) + return nil, err + } + invocation := *invoc + + svc := impl.Service{} + svc.Path = invocation.AttachmentsByKey(constant.PATH_KEY, "") + svc.Interface = invocation.AttachmentsByKey(constant.INTERFACE_KEY, "") + svc.Version = invocation.AttachmentsByKey(constant.VERSION_KEY, "") + svc.Group = invocation.AttachmentsByKey(constant.GROUP_KEY, "") + svc.Method = invocation.MethodName() + timeout, err := strconv.Atoi(invocation.AttachmentsByKey(constant.TIMEOUT_KEY, strconv.Itoa(constant.DEFAULT_REMOTING_TIMEOUT))) + if err != nil { + // it will be wrapped in readwrite.Write . + return nil, perrors.WithStack(err) + } + svc.Timeout = time.Duration(timeout) + + header := impl.DubboHeader{} + serialization := invocation.AttachmentsByKey(constant.SERIALIZATION_KEY, constant.HESSIAN2_SERIALIZATION) + if serialization == constant.PROTOBUF_SERIALIZATION { + header.SerialID = constant.S_Proto + } else { + header.SerialID = constant.S_Hessian2 + } + header.ID = request.ID + if request.TwoWay { + header.Type = impl.PackageRequest_TwoWay + } else { + header.Type = impl.PackageRequest + } + + pkg := &impl.DubboPackage{ + Header: header, + Service: svc, + Body: impl.NewRequestPayload(invocation.Arguments(), invocation.Attachments()), + Err: nil, + Codec: impl.NewDubboCodec(nil), + } + + if err := impl.LoadSerializer(pkg); err != nil { + return nil, perrors.WithStack(err) + } + + return pkg.Marshal() +} + +// encode heartbeart request +func (c *DubboCodec) encodeHeartbeartReqeust(request *remoting.Request) (*bytes.Buffer, error) { + header := impl.DubboHeader{ + Type: impl.PackageHeartbeat, + SerialID: constant.S_Hessian2, + ID: request.ID, + } + + pkg := &impl.DubboPackage{ + Header: header, + Service: impl.Service{}, + Body: impl.NewRequestPayload([]interface{}{}, nil), + Err: nil, + Codec: impl.NewDubboCodec(nil), + } + + if err := impl.LoadSerializer(pkg); err != nil { + return nil, err + } + return pkg.Marshal() +} + +// encode response +func (c *DubboCodec) EncodeResponse(response *remoting.Response) (*bytes.Buffer, error) { + var ptype = impl.PackageResponse + if response.IsHeartbeat() { + ptype = impl.PackageHeartbeat + } + resp := &impl.DubboPackage{ + Header: impl.DubboHeader{ + SerialID: response.SerialID, + Type: ptype, + ID: response.ID, + ResponseStatus: response.Status, + }, + } + if !response.IsHeartbeat() { + resp.Body = &impl.ResponsePayload{ + RspObj: response.Result.(protocol.RPCResult).Rest, + Exception: response.Result.(protocol.RPCResult).Err, + Attachments: response.Result.(protocol.RPCResult).Attrs, + } + } + + codec := impl.NewDubboCodec(nil) + + pkg, err := codec.Encode(*resp) + if err != nil { + return nil, perrors.WithStack(err) + } + + return bytes.NewBuffer(pkg), nil +} + +// Decode data, including request and response. +func (c *DubboCodec) Decode(data []byte) (remoting.DecodeResult, int, error) { + if c.isRequest(data) { + req, len, err := c.decodeRequest(data) + if err != nil { + return remoting.DecodeResult{}, len, perrors.WithStack(err) + } + return remoting.DecodeResult{IsRequest: true, Result: req}, len, perrors.WithStack(err) + } + + resp, len, err := c.decodeResponse(data) + if err != nil { + return remoting.DecodeResult{}, len, perrors.WithStack(err) + } + return remoting.DecodeResult{IsRequest: false, Result: resp}, len, perrors.WithStack(err) +} + +func (c *DubboCodec) isRequest(data []byte) bool { + if data[2]&byte(0x80) == 0x00 { + return false + } + return true +} + +// decode request +func (c *DubboCodec) decodeRequest(data []byte) (*remoting.Request, int, error) { + var request *remoting.Request = nil + buf := bytes.NewBuffer(data) + pkg := impl.NewDubboPackage(buf) + pkg.SetBody(make([]interface{}, 7)) + err := pkg.Unmarshal() + if err != nil { + originErr := perrors.Cause(err) + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + //FIXME + return nil, 0, originErr + } + logger.Errorf("pkg.Unmarshal(len(@data):%d) = error:%+v", buf.Len(), err) + + return request, 0, perrors.WithStack(err) + } + request = &remoting.Request{ + ID: pkg.Header.ID, + SerialID: pkg.Header.SerialID, + TwoWay: pkg.Header.Type&impl.PackageRequest_TwoWay != 0x00, + Event: pkg.Header.Type&impl.PackageHeartbeat != 0x00, + } + if (pkg.Header.Type & impl.PackageHeartbeat) == 0x00 { + // convert params of request + req := pkg.Body.(map[string]interface{}) + + //invocation := request.Data.(*invocation.RPCInvocation) + var methodName string + var args []interface{} + attachments := make(map[string]interface{}) + if req[impl.DubboVersionKey] != nil { + //dubbo version + request.Version = req[impl.DubboVersionKey].(string) + } + //path + attachments[constant.PATH_KEY] = pkg.Service.Path + //version + attachments[constant.VERSION_KEY] = pkg.Service.Version + //method + methodName = pkg.Service.Method + args = req[impl.ArgsKey].([]interface{}) + attachments = req[impl.AttachmentsKey].(map[string]interface{}) + invoc := invocation.NewRPCInvocationWithOptions(invocation.WithAttachments(attachments), + invocation.WithArguments(args), invocation.WithMethodName(methodName)) + request.Data = invoc + + } + return request, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} + +// decode response +func (c *DubboCodec) decodeResponse(data []byte) (*remoting.Response, int, error) { + buf := bytes.NewBuffer(data) + pkg := impl.NewDubboPackage(buf) + response := &remoting.Response{} + err := pkg.Unmarshal() + if err != nil { + originErr := perrors.Cause(err) + // if the data is very big, so the receive need much times. + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + return nil, 0, originErr + } + logger.Errorf("pkg.Unmarshal(len(@data):%d) = error:%+v", buf.Len(), err) + + return nil, 0, perrors.WithStack(err) + } + response = &remoting.Response{ + ID: pkg.Header.ID, + //Version: pkg.Header., + SerialID: pkg.Header.SerialID, + Status: pkg.Header.ResponseStatus, + Event: (pkg.Header.Type & impl.PackageHeartbeat) != 0, + } + var error error + if pkg.Header.Type&impl.PackageHeartbeat != 0x00 { + if pkg.Header.Type&impl.PackageResponse != 0x00 { + logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", pkg.Header, pkg.Body) + if pkg.Err != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", pkg.Err) + error = pkg.Err + } + } else { + logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", pkg.Header, pkg.Service, pkg.Body) + response.Status = hessian.Response_OK + //reply(session, p, hessian.PackageHeartbeat) + } + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, error + } + logger.Debugf("get rpc response{header: %#v, body: %#v}", pkg.Header, pkg.Body) + rpcResult := &protocol.RPCResult{} + response.Result = rpcResult + if pkg.Header.Type&impl.PackageRequest == 0x00 { + if pkg.Err != nil { + rpcResult.Err = pkg.Err + } else if pkg.Body.(*impl.ResponsePayload).Exception != nil { + rpcResult.Err = pkg.Body.(*impl.ResponsePayload).Exception + response.Error = rpcResult.Err + } + rpcResult.Attrs = pkg.Body.(*impl.ResponsePayload).Attachments + rpcResult.Rest = pkg.Body.(*impl.ResponsePayload).RspObj + } + + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} diff --git a/protocol/dubbo/dubbo_exporter.go b/protocol/dubbo/dubbo_exporter.go index dd80937c5bdf6718c2047b102115d8c08afcd899..1873a63fe14f1e2bbb415d5a22c157d03f9ae6ee 100644 --- a/protocol/dubbo/dubbo_exporter.go +++ b/protocol/dubbo/dubbo_exporter.go @@ -42,10 +42,9 @@ func NewDubboExporter(key string, invoker protocol.Invoker, exporterMap *sync.Ma // 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(interfaceName, DUBBO, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, DUBBO, de.GetInvoker().GetUrl().ServiceKey()) 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 59202d5f49f30acb9e75c4e2f135601285f08e13..acddfd6cd51733794fcf2e93f096d898de7ec1cd 100644 --- a/protocol/dubbo/dubbo_invoker.go +++ b/protocol/dubbo/dubbo_invoker.go @@ -20,6 +20,7 @@ package dubbo import ( "context" "strconv" + "strings" "sync" "sync/atomic" "time" @@ -34,8 +35,10 @@ import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/protocol" invocation_impl "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" ) var ( @@ -46,24 +49,35 @@ var ( ) var ( - attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY} + attachmentKey = []string{constant.INTERFACE_KEY, constant.GROUP_KEY, constant.TOKEN_KEY, constant.TIMEOUT_KEY, + constant.VERSION_KEY} ) -// DubboInvoker is dubbo client invoker. +// DubboInvoker is implement of protocol.Invoker. A dubboInvoker refer to one service and ip. type DubboInvoker struct { protocol.BaseInvoker - client *Client + // the exchange layer, it is focus on network communication. + client *remoting.ExchangeClient quitOnce sync.Once + // timeout for service(interface) level. + timeout time.Duration // Used to record the number of requests. -1 represent this DubboInvoker is destroyed reqNum int64 } -// NewDubboInvoker create dubbo client invoker. -func NewDubboInvoker(url common.URL, client *Client) *DubboInvoker { +// NewDubboInvoker constructor +func NewDubboInvoker(url *common.URL, client *remoting.ExchangeClient) *DubboInvoker { + requestTimeout := config.GetConsumerConfig().RequestTimeout + + requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) + if t, err := time.ParseDuration(requestTimeoutStr); err == nil { + requestTimeout = t + } return &DubboInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: client, reqNum: 0, + timeout: requestTimeout, } } @@ -84,6 +98,8 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati defer atomic.AddInt64(&(di.reqNum), -1) inv := invocation.(*invocation_impl.RPCInvocation) + // init param + inv.SetAttachments(constant.PATH_KEY, di.GetUrl().GetParam(constant.INTERFACE_KEY, "")) for _, k := range attachmentKey { if v := di.GetUrl().GetParam(k, ""); len(v) > 0 { inv.SetAttachments(k, v) @@ -94,35 +110,61 @@ func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocati di.appendCtx(ctx, inv) url := di.GetUrl() + // default hessian2 serialization, compatible + if url.GetParam(constant.SERIALIZATION_KEY, "") == "" { + url.SetParam(constant.SERIALIZATION_KEY, constant.HESSIAN2_SERIALIZATION) + } // async async, err := strconv.ParseBool(inv.AttachmentsByKey(constant.ASYNC_KEY, "false")) if err != nil { logger.Errorf("ParseBool - error: %v", err) async = false } - response := NewResponse(inv.Reply(), nil) + //response := NewResponse(inv.Reply(), nil) + rest := &protocol.RPCResult{} + timeout := di.getTimeout(inv) if async { if callBack, ok := inv.CallBack().(func(response common.CallbackResponse)); ok { - result.Err = di.client.AsyncCall(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), callBack, response) + //result.Err = di.client.AsyncCall(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), callBack, response) + result.Err = di.client.AsyncRequest(&invocation, url, timeout, callBack, rest) } else { - result.Err = di.client.CallOneway(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments())) + result.Err = di.client.Send(&invocation, url, timeout) } } else { if inv.Reply() == nil { result.Err = ErrNoReply } else { - result.Err = di.client.Call(NewRequest(url.Location, url, inv.MethodName(), inv.Arguments(), inv.Attachments()), response) + result.Err = di.client.Request(&invocation, url, timeout, rest) } } if result.Err == nil { result.Rest = inv.Reply() - result.Attrs = response.atta + result.Attrs = rest.Attrs } logger.Debugf("result.Err: %v, result.Rest: %v", result.Err, result.Rest) return &result } +// get timeout including methodConfig +func (di *DubboInvoker) getTimeout(invocation *invocation_impl.RPCInvocation) time.Duration { + var timeout = di.GetUrl().GetParam(strings.Join([]string{constant.METHOD_KEYS, invocation.MethodName(), constant.TIMEOUT_KEY}, "."), "") + if len(timeout) != 0 { + if t, err := time.ParseDuration(timeout); err == nil { + // config timeout into attachment + invocation.SetAttachments(constant.TIMEOUT_KEY, strconv.Itoa(int(t.Milliseconds()))) + return t + } + } + // set timeout into invocation at method level + invocation.SetAttachments(constant.TIMEOUT_KEY, strconv.Itoa(int(di.timeout.Milliseconds()))) + return di.timeout +} + +func (di *DubboInvoker) IsAvailable() bool { + return di.client.IsAvailable() +} + // Destroy destroy dubbo client invoker. func (di *DubboInvoker) Destroy() { di.quitOnce.Do(func() { @@ -150,8 +192,7 @@ func (di *DubboInvoker) appendCtx(ctx context.Context, inv *invocation_impl.RPCI // inject opentracing ctx currentSpan := opentracing.SpanFromContext(ctx) if currentSpan != nil { - carrier := opentracing.TextMapCarrier(inv.Attachments()) - err := opentracing.GlobalTracer().Inject(currentSpan.Context(), opentracing.TextMap, carrier) + err := injectTraceCtx(currentSpan, inv) if err != nil { logger.Errorf("Could not inject the span context into attachments: %v", err) } diff --git a/protocol/dubbo/dubbo_invoker_test.go b/protocol/dubbo/dubbo_invoker_test.go index c0640d5558fcb9fb00f02eba0fddc54bb4162592..c7a9a2697529d096e57585347b0a8b0a535451b0 100644 --- a/protocol/dubbo/dubbo_invoker_test.go +++ b/protocol/dubbo/dubbo_invoker_test.go @@ -18,6 +18,7 @@ package dubbo import ( + "bytes" "context" "sync" "testing" @@ -25,40 +26,37 @@ import ( ) import ( + hessian "github.com/apache/dubbo-go-hessian2" "github.com/opentracing/opentracing-go" + perrors "github.com/pkg/errors" "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/proxy/proxy_factory" + "github.com/apache/dubbo-go/protocol" "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/getty" ) func TestDubboInvokerInvoke(t *testing.T) { proto, url := InitTest(t) - c := &Client{ - pendingResponses: new(sync.Map), - conf: *clientConf, - opts: Options{ - ConnectTimeout: 3 * time.Second, - RequestTimeout: 6 * time.Second, - }, - } - c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) + c := getExchangeClient(url) invoker := NewDubboInvoker(url, c) user := &User{} - inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(mockMethodNameGetUser), invocation.WithArguments([]interface{}{"1", "username"}), - invocation.WithReply(user), invocation.WithAttachments(map[string]string{"test_key": "test_value"})) + inv := invocation.NewRPCInvocationWithOptions(invocation.WithMethodName("GetUser"), invocation.WithArguments([]interface{}{"1", "username"}), + invocation.WithReply(user), invocation.WithAttachments(map[string]interface{}{"test_key": "test_value"})) // Call res := invoker.Invoke(context.Background(), inv) assert.NoError(t, res.Error()) assert.Equal(t, User{Id: "1", Name: "username"}, *res.Result().(*User)) - assert.Equal(t, "test_value", res.Attachments()["test_key"]) // test attachments for request/response // CallOneway inv.SetAttachments(constant.ASYNC_KEY, "true") @@ -69,8 +67,10 @@ func TestDubboInvokerInvoke(t *testing.T) { lock := sync.Mutex{} lock.Lock() inv.SetCallBack(func(response common.CallbackResponse) { - r := response.(AsyncCallbackResponse) - assert.Equal(t, User{Id: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) + r := response.(remoting.AsyncCallbackResponse) + rst := *r.Reply.(*remoting.Response).Result.(*protocol.RPCResult) + assert.Equal(t, User{Id: "1", Name: "username"}, *(rst.Rest.(*User))) + //assert.Equal(t, User{ID: "1", Name: "username"}, *r.Reply.(*Response).reply.(*User)) lock.Unlock() }) res = invoker.Invoke(context.Background(), inv) @@ -89,6 +89,145 @@ func TestDubboInvokerInvoke(t *testing.T) { // destroy lock.Lock() + defer lock.Unlock() proto.Destroy() - lock.Unlock() +} + +func InitTest(t *testing.T) (protocol.Protocol, *common.URL) { + + hessian.RegisterPOJO(&User{}) + + 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) + + // config + getty.SetClientConf(getty.ClientConfig{ + ConnectionNum: 2, + HeartbeatPeriod: "5s", + SessionTimeout: "20s", + PoolTTL: 600, + PoolSize: 64, + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "4s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "client", + }, + }) + getty.SetServerConfig(getty.ServerConfig{ + SessionNumber: 700, + SessionTimeout: "20s", + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "1s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "server", + }}) + + // Export + proto := GetProtocol() + url, err := common.NewURL("dubbo://127.0.0.1:20702/UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + assert.NoError(t, err) + proto.Export(&proxy_factory.ProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + }) + + time.Sleep(time.Second * 2) + + return proto, url +} + +////////////////////////////////// +// provider +////////////////////////////////// + +type ( + User struct { + Id string `json:"id"` + Name string `json:"name"` + } + + UserProvider struct { + user map[string]User + } +) + +// size:4801228 +func (u *UserProvider) GetBigPkg(ctx context.Context, req []interface{}, rsp *User) error { + argBuf := new(bytes.Buffer) + for i := 0; i < 800; i++ { + // use chinese for test + argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") + } + rsp.Id = argBuf.String() + rsp.Name = argBuf.String() + return nil +} + +func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User) error { + rsp.Id = req[0].(string) + rsp.Name = req[1].(string) + return nil +} + +func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { + return User{Id: id, Name: name}, nil +} + +func (u *UserProvider) GetUser1() error { + return nil +} + +func (u *UserProvider) GetUser2() error { + return perrors.New("error") +} + +func (u *UserProvider) GetUser3(rsp *[]interface{}) error { + *rsp = append(*rsp, User{Id: "1", Name: "username"}) + return nil +} + +func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) ([]interface{}, error) { + + return []interface{}{User{Id: req[0].([]interface{})[0].(string), Name: req[0].([]interface{})[1].(string)}}, nil +} + +func (u *UserProvider) GetUser5(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { + return map[interface{}]interface{}{"key": User{Id: req[0].(map[interface{}]interface{})["id"].(string), Name: req[0].(map[interface{}]interface{})["name"].(string)}}, nil +} + +func (u *UserProvider) GetUser6(id int64) (*User, error) { + if id == 0 { + return nil, nil + } + return &User{Id: "1"}, nil +} + +func (u *UserProvider) Reference() string { + return "UserProvider" +} + +func (u User) JavaClassName() string { + return "com.ikurento.user.User" } diff --git a/protocol/dubbo/dubbo_protocol.go b/protocol/dubbo/dubbo_protocol.go index 9eeefd079279d82241da8e21df5edfe77b8003e0..4f03b8aba061ea9b37b35d89142eb7bec80f3a97 100644 --- a/protocol/dubbo/dubbo_protocol.go +++ b/protocol/dubbo/dubbo_protocol.go @@ -18,10 +18,16 @@ package dubbo import ( + "context" + "fmt" "sync" "time" ) +import ( + "github.com/opentracing/opentracing-go" +) + import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" @@ -29,14 +35,23 @@ import ( "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" + "github.com/apache/dubbo-go/remoting/getty" ) -// dubbo protocol constant const ( // DUBBO is dubbo protocol name DUBBO = "dubbo" ) +var ( + // Make the connection can be shared. + // It will create one connection for one address (ip+port) + exchangeClientMap = new(sync.Map) + exchangeLock = new(sync.Map) +) + func init() { extension.SetProtocol(DUBBO, GetProtocol) } @@ -45,10 +60,12 @@ var ( dubboProtocol *DubboProtocol ) -// DubboProtocol is a dubbo protocol implement. +// It support dubbo protocol. It implements Protocol interface for dubbo protocol. type DubboProtocol struct { protocol.BaseProtocol - serverMap map[string]*Server + // It is store relationship about serviceKey(group/interface:version) and ExchangeServer + // The ExchangeServer is introduced to replace of Server. Because Server is depend on getty directly. + serverMap map[string]*remoting.ExchangeServer serverLock sync.Mutex } @@ -56,7 +73,7 @@ type DubboProtocol struct { func NewDubboProtocol() *DubboProtocol { return &DubboProtocol{ BaseProtocol: protocol.NewBaseProtocol(), - serverMap: make(map[string]*Server), + serverMap: make(map[string]*remoting.ExchangeServer), } } @@ -67,26 +84,19 @@ func (dp *DubboProtocol) Export(invoker protocol.Invoker) protocol.Exporter { exporter := NewDubboExporter(serviceKey, invoker, dp.ExporterMap()) dp.SetExporterMap(serviceKey, exporter) logger.Infof("Export service: %s", url.String()) - // start server dp.openServer(url) return exporter } // Refer create dubbo service reference. -func (dp *DubboProtocol) Refer(url common.URL) protocol.Invoker { - //default requestTimeout - var requestTimeout = config.GetConsumerConfig().RequestTimeout - - requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) - if t, err := time.ParseDuration(requestTimeoutStr); err == nil { - requestTimeout = t +func (dp *DubboProtocol) Refer(url *common.URL) protocol.Invoker { + exchangeClient := getExchangeClient(url) + if exchangeClient == nil { + logger.Warnf("can't dial the server: %+v", url.Location) + return nil } - - invoker := NewDubboInvoker(url, NewClient(Options{ - ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, - RequestTimeout: requestTimeout, - })) + invoker := NewDubboInvoker(url, exchangeClient) dp.SetInvokers(invoker) logger.Infof("Refer service: %s", url.String()) return invoker @@ -105,7 +115,7 @@ func (dp *DubboProtocol) Destroy() { } } -func (dp *DubboProtocol) openServer(url common.URL) { +func (dp *DubboProtocol) openServer(url *common.URL) { _, ok := dp.serverMap[url.Location] if !ok { _, ok := dp.ExporterMap().Load(url.ServiceKey()) @@ -116,9 +126,12 @@ func (dp *DubboProtocol) openServer(url common.URL) { dp.serverLock.Lock() _, ok = dp.serverMap[url.Location] if !ok { - srv := NewServer() + handler := func(invocation *invocation.RPCInvocation) protocol.RPCResult { + return doHandleRequest(invocation) + } + srv := remoting.NewExchangeServer(url, getty.NewServer(url, handler)) dp.serverMap[url.Location] = srv - srv.Start(url) + srv.Start() } dp.serverLock.Unlock() } @@ -131,3 +144,91 @@ func GetProtocol() protocol.Protocol { } return dubboProtocol } + +func doHandleRequest(rpcInvocation *invocation.RPCInvocation) protocol.RPCResult { + exporter, _ := dubboProtocol.ExporterMap().Load(rpcInvocation.ServiceKey()) + result := protocol.RPCResult{} + if exporter == nil { + err := fmt.Errorf("don't have this exporter, key: %s", rpcInvocation.ServiceKey()) + logger.Errorf(err.Error()) + result.Err = err + //reply(session, p, hessian.PackageResponse) + return result + } + invoker := exporter.(protocol.Exporter).GetInvoker() + if invoker != nil { + // FIXME + ctx := rebuildCtx(rpcInvocation) + + invokeResult := invoker.Invoke(ctx, rpcInvocation) + if err := invokeResult.Error(); err != nil { + result.Err = invokeResult.Error() + //p.Header.ResponseStatus = hessian.Response_OK + //p.Body = hessian.NewResponse(nil, err, result.Attachments()) + } else { + result.Rest = invokeResult.Result() + //p.Header.ResponseStatus = hessian.Response_OK + //p.Body = hessian.NewResponse(res, nil, result.Attachments()) + } + } else { + result.Err = fmt.Errorf("don't have the invoker, key: %s", rpcInvocation.ServiceKey()) + } + return result +} + +func getExchangeClient(url *common.URL) *remoting.ExchangeClient { + clientTmp, ok := exchangeClientMap.Load(url.Location) + if !ok { + var exchangeClientTmp *remoting.ExchangeClient + func() { + // lock for NewExchangeClient and store into map. + _, loaded := exchangeLock.LoadOrStore(url.Location, 0x00) + // unlock + defer exchangeLock.Delete(url.Location) + if loaded { + // retry for 5 times. + for i := 0; i < 5; i++ { + if clientTmp, ok = exchangeClientMap.Load(url.Location); ok { + break + } else { + // if cannot get, sleep a while. + time.Sleep(time.Duration(i*100) * time.Millisecond) + } + } + return + } + // new ExchangeClient + exchangeClientTmp = remoting.NewExchangeClient(url, getty.NewClient(getty.Options{ + ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, + RequestTimeout: config.GetConsumerConfig().RequestTimeout, + }), config.GetConsumerConfig().ConnectTimeout, false) + // input store + if exchangeClientTmp != nil { + exchangeClientMap.Store(url.Location, exchangeClientTmp) + } + }() + if exchangeClientTmp != nil { + return exchangeClientTmp + } + } + // cannot dial the server + if clientTmp == nil { + return nil + } + return clientTmp.(*remoting.ExchangeClient) +} + +// rebuildCtx rebuild the context by attachment. +// Once we decided to transfer more context's key-value, we should change this. +// now we only support rebuild the tracing context +func rebuildCtx(inv *invocation.RPCInvocation) context.Context { + ctx := context.WithValue(context.Background(), "attachment", inv.Attachments()) + + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(filterContext(inv.Attachments()))) + if err == nil { + ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) + } + return ctx +} diff --git a/protocol/dubbo/dubbo_protocol_test.go b/protocol/dubbo/dubbo_protocol_test.go index 6f3892be67be533dea09dc7bd54de56844dbc79c..9eba90e9da564453649b378f061bc0179ffedc5e 100644 --- a/protocol/dubbo/dubbo_protocol_test.go +++ b/protocol/dubbo/dubbo_protocol_test.go @@ -28,7 +28,9 @@ import ( import ( "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/proxy/proxy_factory" "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/remoting/getty" ) const ( @@ -39,14 +41,56 @@ const ( "side=provider&timeout=3000×tamp=1556509797245" ) -func TestDubboProtocolExport(t *testing.T) { +func initDubboInvokerTest() { + getty.SetServerConfig(getty.ServerConfig{ + SessionNumber: 700, + SessionTimeout: "20s", + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "1s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "server", + }}) + getty.SetClientConf(getty.ClientConfig{ + ConnectionNum: 1, + HeartbeatPeriod: "3s", + SessionTimeout: "20s", + PoolTTL: 600, + PoolSize: 64, + GettySessionParam: getty.GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "4s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "client", + }, + }) +} + +func TestDubboProtocol_Export(t *testing.T) { + initDubboInvokerTest() + srvCfg := getty.GetDefaultServerConfig() + getty.SetServerConfig(srvCfg) // Export proto := GetProtocol() - srvConf = &ServerConfig{} url, err := common.NewURL(mockCommonUrl) assert.NoError(t, err) exporter := proto.Export(protocol.NewBaseInvoker(url)) - // make sure url eq := exporter.GetInvoker().GetUrl().URLEqual(url) assert.True(t, eq) @@ -60,10 +104,10 @@ func TestDubboProtocolExport(t *testing.T) { assert.True(t, eq2) // make sure exporterMap after 'Unexport' - _, ok := proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) + _, ok := proto.(*DubboProtocol).ExporterMap().Load(url2.ServiceKey()) assert.True(t, ok) - exporter.Unexport() - _, ok = proto.(*DubboProtocol).ExporterMap().Load(url.ServiceKey()) + exporter2.Unexport() + _, ok = proto.(*DubboProtocol).ExporterMap().Load(url2.ServiceKey()) assert.False(t, ok) // make sure serverMap after 'Destroy' @@ -74,14 +118,29 @@ func TestDubboProtocolExport(t *testing.T) { assert.False(t, ok) } -func TestDubboProtocolRefer(t *testing.T) { +func TestDubboProtocolReferNoConnect(t *testing.T) { // Refer + initDubboInvokerTest() proto := GetProtocol() url, err := common.NewURL(mockCommonUrl) assert.NoError(t, err) - clientConf = &ClientConfig{} invoker := proto.Refer(url) + assert.Nil(t, invoker) +} + +func TestDubboProtocol_Refer(t *testing.T) { + initDubboInvokerTest() + cliCfg := getty.GetDefaultClientConfig() + getty.SetClientConf(cliCfg) + // Refer + proto := GetProtocol() + url, err := common.NewURL(mockCommonUrl) + proto.Export(&proxy_factory.ProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + }) + assert.NoError(t, err) + invoker := proto.Refer(url) // make sure url eq := invoker.GetUrl().URLEqual(url) assert.True(t, eq) diff --git a/protocol/dubbo/hessian2/const.go b/protocol/dubbo/hessian2/const.go new file mode 100644 index 0000000000000000000000000000000000000000..74a00b601db22397916aab215ccd33bc918d91e7 --- /dev/null +++ b/protocol/dubbo/hessian2/const.go @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This constants are also defined in dubbo constants. But we will still used these until hessian is stable. + +package hessian2 + +import ( + "reflect" + "regexp" +) + +import ( + perrors "github.com/pkg/errors" +) + +const ( + mask = byte(127) + flag = byte(128) +) + +const ( + // Zero : byte zero + Zero = byte(0x00) +) + +// constansts +const ( + TAG_READ = int32(-1) + ASCII_GAP = 32 + CHUNK_SIZE = 4096 + BC_BINARY = byte('B') // final chunk + BC_BINARY_CHUNK = byte('A') // non-final chunk + + BC_BINARY_DIRECT = byte(0x20) // 1-byte length binary + BINARY_DIRECT_MAX = byte(0x0f) + BC_BINARY_SHORT = byte(0x34) // 2-byte length binary + BINARY_SHORT_MAX = 0x3ff // 0-1023 binary + + BC_DATE = byte(0x4a) // 64-bit millisecond UTC date + BC_DATE_MINUTE = byte(0x4b) // 32-bit minute UTC date + + BC_DOUBLE = byte('D') // IEEE 64-bit double + + BC_DOUBLE_ZERO = byte(0x5b) + BC_DOUBLE_ONE = byte(0x5c) + BC_DOUBLE_BYTE = byte(0x5d) + BC_DOUBLE_SHORT = byte(0x5e) + BC_DOUBLE_MILL = byte(0x5f) + + BC_FALSE = byte('F') // boolean false + + BC_INT = byte('I') // 32-bit int + + INT_DIRECT_MIN = -0x10 + INT_DIRECT_MAX = byte(0x2f) + BC_INT_ZERO = byte(0x90) + + INT_BYTE_MIN = -0x800 + INT_BYTE_MAX = 0x7ff + BC_INT_BYTE_ZERO = byte(0xc8) + + BC_END = byte('Z') + + INT_SHORT_MIN = -0x40000 + INT_SHORT_MAX = 0x3ffff + BC_INT_SHORT_ZERO = byte(0xd4) + + BC_LIST_VARIABLE = byte(0x55) + BC_LIST_FIXED = byte('V') + BC_LIST_VARIABLE_UNTYPED = byte(0x57) + BC_LIST_FIXED_UNTYPED = byte(0x58) + _listFixedTypedLenTagMin = byte(0x70) + _listFixedTypedLenTagMax = byte(0x77) + _listFixedUntypedLenTagMin = byte(0x78) + _listFixedUntypedLenTagMax = byte(0x7f) + + BC_LIST_DIRECT = byte(0x70) + BC_LIST_DIRECT_UNTYPED = byte(0x78) + LIST_DIRECT_MAX = byte(0x7) + + BC_LONG = byte('L') // 64-bit signed integer + LONG_DIRECT_MIN = -0x08 + LONG_DIRECT_MAX = byte(0x0f) + BC_LONG_ZERO = byte(0xe0) + + LONG_BYTE_MIN = -0x800 + LONG_BYTE_MAX = 0x7ff + BC_LONG_BYTE_ZERO = byte(0xf8) + + LONG_SHORT_MIN = -0x40000 + LONG_SHORT_MAX = 0x3ffff + BC_LONG_SHORT_ZERO = byte(0x3c) + + BC_LONG_INT = byte(0x59) + + BC_MAP = byte('M') + BC_MAP_UNTYPED = byte('H') + + BC_NULL = byte('N') // x4e + + BC_OBJECT = byte('O') + BC_OBJECT_DEF = byte('C') + + BC_OBJECT_DIRECT = byte(0x60) + OBJECT_DIRECT_MAX = byte(0x0f) + + BC_REF = byte(0x51) + + BC_STRING = byte('S') // final string + BC_STRING_CHUNK = byte('R') // non-final string + + BC_STRING_DIRECT = byte(0x00) + STRING_DIRECT_MAX = byte(0x1f) + BC_STRING_SHORT = byte(0x30) + STRING_SHORT_MAX = 0x3ff + + BC_TRUE = byte('T') + + P_PACKET_CHUNK = byte(0x4f) + P_PACKET = byte('P') + + P_PACKET_DIRECT = byte(0x80) + PACKET_DIRECT_MAX = byte(0x7f) + + P_PACKET_SHORT = byte(0x70) + PACKET_SHORT_MAX = 0xfff + ARRAY_STRING = "[string" + ARRAY_INT = "[int" + ARRAY_DOUBLE = "[double" + ARRAY_FLOAT = "[float" + ARRAY_BOOL = "[boolean" + ARRAY_LONG = "[long" + + PATH_KEY = "path" + GROUP_KEY = "group" + INTERFACE_KEY = "interface" + VERSION_KEY = "version" + TIMEOUT_KEY = "timeout" + + STRING_NIL = "" + STRING_TRUE = "true" + STRING_FALSE = "false" + STRING_ZERO = "0.0" + STRING_ONE = "1.0" +) + +// DubboResponse related consts +const ( + Response_OK byte = 20 + Response_CLIENT_TIMEOUT byte = 30 + Response_SERVER_TIMEOUT byte = 31 + Response_BAD_REQUEST byte = 40 + Response_BAD_RESPONSE byte = 50 + Response_SERVICE_NOT_FOUND byte = 60 + Response_SERVICE_ERROR byte = 70 + Response_SERVER_ERROR byte = 80 + Response_CLIENT_ERROR byte = 90 + + // According to "java dubbo" There are two cases of response: + // 1. with attachments + // 2. no attachments + RESPONSE_WITH_EXCEPTION int32 = 0 + RESPONSE_VALUE int32 = 1 + RESPONSE_NULL_VALUE int32 = 2 + RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS int32 = 3 + RESPONSE_VALUE_WITH_ATTACHMENTS int32 = 4 + RESPONSE_NULL_VALUE_WITH_ATTACHMENTS int32 = 5 +) + +/** + * the dubbo protocol header length is 16 Bytes. + * the first 2 Bytes is magic code '0xdabb' + * the next 1 Byte is message flags, in which its 16-20 bit is serial id, 21 for event, 22 for two way, 23 for request/response flag + * the next 1 Bytes is response state. + * the next 8 Bytes is package DI. + * the next 4 Bytes is package length. + **/ +const ( + // header length. + HEADER_LENGTH = 16 + + // magic header + MAGIC = uint16(0xdabb) + MAGIC_HIGH = byte(0xda) + MAGIC_LOW = byte(0xbb) + + // message flag. + FLAG_REQUEST = byte(0x80) + FLAG_TWOWAY = byte(0x40) + FLAG_EVENT = byte(0x20) // for heartbeat + SERIAL_MASK = 0x1f + + DUBBO_VERSION = "2.5.4" + DUBBO_VERSION_KEY = "dubbo" + DEFAULT_DUBBO_PROTOCOL_VERSION = "2.0.2" // Dubbo RPC protocol version, for compatibility, it must not be between 2.0.10 ~ 2.6.2 + LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT = 2000200 + DEFAULT_LEN = 8388608 // 8 * 1024 * 1024 default body max length +) + +// regular +const ( + JAVA_IDENT_REGEX = "(?:[_$a-zA-Z][_$a-zA-Z0-9]*)" + CLASS_DESC = "(?:L" + JAVA_IDENT_REGEX + "(?:\\/" + JAVA_IDENT_REGEX + ")*;)" + ARRAY_DESC = "(?:\\[+(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "))" + DESC_REGEX = "(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "|" + ARRAY_DESC + ")" +) + +// Dubbo request response related consts +var ( + DubboRequestHeaderBytesTwoWay = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY} + DubboRequestHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST} + DubboResponseHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, Zero, Response_OK} + DubboRequestHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY | FLAG_EVENT} + DubboResponseHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_EVENT} +) + +// Error part +var ( + ErrHeaderNotEnough = perrors.New("header buffer too short") + ErrBodyNotEnough = perrors.New("body buffer too short") + ErrJavaException = perrors.New("got java exception") + ErrIllegalPackage = perrors.New("illegal package!") +) + +// nolint +var DescRegex, _ = regexp.Compile(DESC_REGEX) + +var NilValue = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()) diff --git a/protocol/dubbo/hessian2/hessian_dubbo.go b/protocol/dubbo/hessian2/hessian_dubbo.go new file mode 100644 index 0000000000000000000000000000000000000000..1afa4ec96eccbb8077852dfcc020e0eb05be3257 --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_dubbo.go @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hessian2 + +import ( + "bufio" + "encoding/binary" + "time" +) + +import ( + "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +// enum part +const ( + PackageError = PackageType(0x01) + PackageRequest = PackageType(0x02) + PackageResponse = PackageType(0x04) + PackageHeartbeat = PackageType(0x08) + PackageRequest_TwoWay = PackageType(0x10) + PackageResponse_Exception = PackageType(0x20) + PackageType_BitSize = 0x2f +) + +// PackageType nolint +type PackageType int + +// DubboHeader dubbo header +type DubboHeader struct { + SerialID byte + Type PackageType + ID int64 + BodyLen int + ResponseStatus byte +} + +// Service defines service instance +type Service struct { + Path string + Interface string + Group string + Version string + Method string + Timeout time.Duration // request timeout +} + +// HessianCodec defines hessian codec +type HessianCodec struct { + pkgType PackageType + reader *bufio.Reader + bodyLen int +} + +// NewHessianCodec generate a new hessian codec instance +func NewHessianCodec(reader *bufio.Reader) *HessianCodec { + return &HessianCodec{ + reader: reader, + } +} + +// NewHessianCodec generate a new hessian codec instance +func NewHessianCodecCustom(pkgType PackageType, reader *bufio.Reader, bodyLen int) *HessianCodec { + return &HessianCodec{ + pkgType: pkgType, + reader: reader, + bodyLen: bodyLen, + } +} + +func (h *HessianCodec) Write(service Service, header DubboHeader, body interface{}) ([]byte, error) { + switch header.Type { + case PackageHeartbeat: + if header.ResponseStatus == Zero { + return packRequest(service, header, body) + } + return packResponse(header, body) + + case PackageRequest, PackageRequest_TwoWay: + return packRequest(service, header, body) + + case PackageResponse: + return packResponse(header, body) + + default: + return nil, perrors.Errorf("Unrecognised message type: %v", header.Type) + } + + // unreachable return nil, nil +} + +// ReadHeader uses hessian codec to read dubbo header +func (h *HessianCodec) ReadHeader(header *DubboHeader) error { + + var err error + + if h.reader.Size() < HEADER_LENGTH { + return ErrHeaderNotEnough + } + buf, err := h.reader.Peek(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + _, err = h.reader.Discard(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + + //// read header + + if buf[0] != MAGIC_HIGH && buf[1] != MAGIC_LOW { + return ErrIllegalPackage + } + + // Header{serialization id(5 bit), event, two way, req/response} + if header.SerialID = buf[2] & SERIAL_MASK; header.SerialID == Zero { + return perrors.Errorf("serialization ID:%v", header.SerialID) + } + + flag := buf[2] & FLAG_EVENT + if flag != Zero { + header.Type |= PackageHeartbeat + } + flag = buf[2] & FLAG_REQUEST + if flag != Zero { + header.Type |= PackageRequest + flag = buf[2] & FLAG_TWOWAY + if flag != Zero { + header.Type |= PackageRequest_TwoWay + } + } else { + header.Type |= PackageResponse + header.ResponseStatus = buf[3] + if header.ResponseStatus != Response_OK { + header.Type |= PackageResponse_Exception + } + } + + // Header{req id} + header.ID = int64(binary.BigEndian.Uint64(buf[4:])) + + // Header{body len} + header.BodyLen = int(binary.BigEndian.Uint32(buf[12:])) + if header.BodyLen < 0 { + return ErrIllegalPackage + } + + h.pkgType = header.Type + h.bodyLen = header.BodyLen + + if h.reader.Buffered() < h.bodyLen { + return ErrBodyNotEnough + } + + return perrors.WithStack(err) + +} + +// ReadBody uses hessian codec to read response body +func (h *HessianCodec) ReadBody(rspObj interface{}) error { + + if h.reader.Buffered() < h.bodyLen { + return ErrBodyNotEnough + } + buf, err := h.reader.Peek(h.bodyLen) + if err != nil { + return perrors.WithStack(err) + } + _, err = h.reader.Discard(h.bodyLen) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + + switch h.pkgType & PackageType_BitSize { + case PackageResponse | PackageHeartbeat | PackageResponse_Exception, PackageResponse | PackageResponse_Exception: + decoder := hessian.NewDecoder(buf[:]) + exception, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + rsp, ok := rspObj.(*DubboResponse) + if !ok { + return perrors.Errorf("java exception:%s", exception.(string)) + } + rsp.Exception = perrors.Errorf("java exception:%s", exception.(string)) + return nil + case PackageRequest | PackageHeartbeat, PackageResponse | PackageHeartbeat: + case PackageRequest: + if rspObj != nil { + if err = unpackRequestBody(hessian.NewDecoder(buf[:]), rspObj); err != nil { + return perrors.WithStack(err) + } + } + case PackageResponse: + if rspObj != nil { + if err = unpackResponseBody(hessian.NewDecoder(buf[:]), rspObj); err != nil { + return perrors.WithStack(err) + } + } + } + + return nil +} + +// ignore body, but only read attachments +func (h *HessianCodec) ReadAttachments() (map[string]interface{}, error) { + if h.reader.Buffered() < h.bodyLen { + return nil, ErrBodyNotEnough + } + buf, err := h.reader.Peek(h.bodyLen) + if err != nil { + return nil, perrors.WithStack(err) + } + _, err = h.reader.Discard(h.bodyLen) + if err != nil { // this is impossible + return nil, perrors.WithStack(err) + } + + switch h.pkgType & PackageType_BitSize { + case PackageRequest: + rspObj := make([]interface{}, 7) + if err = unpackRequestBody(hessian.NewDecoderWithSkip(buf[:]), rspObj); err != nil { + return nil, perrors.WithStack(err) + } + return rspObj[6].(map[string]interface{}), nil + case PackageResponse: + rspObj := &DubboResponse{} + if err = unpackResponseBody(hessian.NewDecoderWithSkip(buf[:]), rspObj); err != nil { + return nil, perrors.WithStack(err) + } + return rspObj.Attachments, nil + } + + return nil, nil +} diff --git a/protocol/dubbo/hessian2/hessian_dubbo_test.go b/protocol/dubbo/hessian2/hessian_dubbo_test.go new file mode 100644 index 0000000000000000000000000000000000000000..13dab92ba874fb5d746c5d57134592865cbfc7bd --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_dubbo_test.go @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hessian2 + +import ( + "bufio" + "bytes" + "reflect" + "testing" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/stretchr/testify/assert" +) + +type Case struct { + A string + B int +} + +type CaseA struct { + A string + B int + C Case +} + +type CaseB struct { + A string + B CaseA +} + +func (c *CaseB) JavaClassName() string { + return "com.test.caseb" +} + +func (c CaseA) JavaClassName() string { + return "com.test.casea" +} + +//JavaClassName java fully qualified path +func (c Case) JavaClassName() string { + return "com.test.case" +} + +func doTestHessianEncodeHeader(t *testing.T, packageType PackageType, responseStatus byte, body interface{}) ([]byte, error) { + hessian.RegisterPOJO(&Case{}) + codecW := NewHessianCodec(nil) + resp, err := codecW.Write(Service{ + Path: "test", + Interface: "ITest", + Version: "v1.0", + Method: "test", + Timeout: time.Second * 10, + }, DubboHeader{ + SerialID: 2, + Type: packageType, + ID: 1, + ResponseStatus: responseStatus, + }, body) + assert.Nil(t, err) + return resp, err +} + +func doTestResponse(t *testing.T, packageType PackageType, responseStatus byte, body interface{}, decodedResponse *DubboResponse, assertFunc func()) { + resp, err := doTestHessianEncodeHeader(t, packageType, responseStatus, body) + + codecR := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + + h := &DubboHeader{} + err = codecR.ReadHeader(h) + assert.Nil(t, err) + + assert.Equal(t, byte(2), h.SerialID) + assert.Equal(t, packageType, h.Type&(PackageRequest|PackageResponse|PackageHeartbeat)) + assert.Equal(t, int64(1), h.ID) + assert.Equal(t, responseStatus, h.ResponseStatus) + + err = codecR.ReadBody(decodedResponse) + assert.Nil(t, err) + t.Log(decodedResponse) + + if assertFunc != nil { + assertFunc() + return + } + + if h.ResponseStatus != Zero && h.ResponseStatus != Response_OK { + assert.Equal(t, "java exception:"+body.(string), decodedResponse.Exception.Error()) + return + } + + in, _ := hessian.EnsureInterface(hessian.UnpackPtrValue(hessian.EnsurePackValue(body)), nil) + out, _ := hessian.EnsureInterface(hessian.UnpackPtrValue(hessian.EnsurePackValue(decodedResponse.RspObj)), nil) + assert.Equal(t, in, out) +} + +func TestResponse(t *testing.T) { + caseObj := Case{A: "a", B: 1} + decodedResponse := &DubboResponse{} + + arr := []*Case{&caseObj} + var arrRes []interface{} + decodedResponse.RspObj = &arrRes + doTestResponse(t, PackageResponse, Response_OK, arr, decodedResponse, func() { + assert.Equal(t, 1, len(arrRes)) + assert.Equal(t, &caseObj, arrRes[0]) + }) + + decodedResponse.RspObj = &Case{} + doTestResponse(t, PackageResponse, Response_OK, &Case{A: "a", B: 1}, decodedResponse, nil) + + s := "ok!!!!!" + strObj := "" + decodedResponse.RspObj = &strObj + doTestResponse(t, PackageResponse, Response_OK, s, decodedResponse, nil) + + var intObj int64 + decodedResponse.RspObj = &intObj + doTestResponse(t, PackageResponse, Response_OK, int64(3), decodedResponse, nil) + + boolObj := false + decodedResponse.RspObj = &boolObj + doTestResponse(t, PackageResponse, Response_OK, true, decodedResponse, nil) + + strObj = "" + decodedResponse.RspObj = &strObj + doTestResponse(t, PackageResponse, hessian.Response_SERVER_ERROR, "error!!!!!", decodedResponse, nil) + + mapObj := map[string][]*Case{"key": {&caseObj}} + mapRes := map[interface{}]interface{}{} + decodedResponse.RspObj = &mapRes + doTestResponse(t, PackageResponse, Response_OK, mapObj, decodedResponse, func() { + c, ok := mapRes["key"] + if !ok { + assert.FailNow(t, "no key in decoded response map") + } + + mapValueArr, ok := c.([]*Case) + if !ok { + assert.FailNow(t, "invalid decoded response map value", "expect []*Case, but get %v", reflect.TypeOf(c)) + } + assert.Equal(t, 1, len(mapValueArr)) + assert.Equal(t, &caseObj, mapValueArr[0]) + }) +} + +func doTestRequest(t *testing.T, packageType PackageType, responseStatus byte, body interface{}) { + resp, err := doTestHessianEncodeHeader(t, packageType, responseStatus, body) + + codecR := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + + h := &DubboHeader{} + err = codecR.ReadHeader(h) + assert.Nil(t, err) + assert.Equal(t, byte(2), h.SerialID) + assert.Equal(t, packageType, h.Type&(PackageRequest|PackageResponse|PackageHeartbeat)) + assert.Equal(t, int64(1), h.ID) + assert.Equal(t, responseStatus, h.ResponseStatus) + + c := make([]interface{}, 7) + err = codecR.ReadBody(c) + assert.Nil(t, err) + t.Log(c) + assert.True(t, len(body.([]interface{})) == len(c[5].([]interface{}))) +} + +func TestRequest(t *testing.T) { + doTestRequest(t, PackageRequest, Zero, []interface{}{"a"}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", true}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3, true}) + doTestRequest(t, PackageRequest, Zero, []interface{}{3.2, true}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3, true, &Case{A: "a", B: 3}}) + doTestRequest(t, PackageRequest, Zero, []interface{}{"a", 3, true, []*Case{{A: "a", B: 3}}}) + doTestRequest(t, PackageRequest, Zero, []interface{}{map[string][]*Case{"key": {{A: "a", B: 3}}}}) +} + +func TestHessianCodec_ReadAttachments(t *testing.T) { + hessian.RegisterPOJO(&AttachTestObject{}) + body := &DubboResponse{ + RspObj: &CaseB{A: "A", B: CaseA{A: "a", B: 1, C: Case{A: "c", B: 2}}}, + Exception: nil, + Attachments: map[string]interface{}{DUBBO_VERSION_KEY: "2.6.4", "att": AttachTestObject{Id: 23, Name: "haha"}}, + } + resp, err := doTestHessianEncodeHeader(t, PackageResponse, Response_OK, body) + assert.NoError(t, err) + hessian.UnRegisterPOJOs(&CaseB{}, &CaseA{}) + codecR1 := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + codecR2 := NewHessianCodec(bufio.NewReader(bytes.NewReader(resp))) + h := &DubboHeader{} + assert.NoError(t, codecR1.ReadHeader(h)) + t.Log(h) + assert.NoError(t, codecR2.ReadHeader(h)) + t.Log(h) + + err = codecR1.ReadBody(body) + assert.Equal(t, "can not find go type name com.test.caseb in registry", err.Error()) + attrs, err := codecR2.ReadAttachments() + assert.NoError(t, err) + assert.Equal(t, "2.6.4", attrs[DUBBO_VERSION_KEY]) + assert.Equal(t, AttachTestObject{Id: 23, Name: "haha"}, *(attrs["att"].(*AttachTestObject))) + assert.NotEqual(t, AttachTestObject{Id: 24, Name: "nohaha"}, *(attrs["att"].(*AttachTestObject))) + + t.Log(attrs) +} + +type AttachTestObject struct { + Id int32 + Name string `dubbo:"name"` +} + +func (AttachTestObject) JavaClassName() string { + return "com.test.Test" +} diff --git a/protocol/dubbo/hessian2/hessian_request.go b/protocol/dubbo/hessian2/hessian_request.go new file mode 100644 index 0000000000000000000000000000000000000000..4ebb4aa1be05d4d1941661fed452dda06cf55fa0 --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_request.go @@ -0,0 +1,350 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hessian2 + +import ( + "encoding/binary" + "reflect" + "strconv" + "strings" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +///////////////////////////////////////// +// dubbo +///////////////////////////////////////// + +func getArgType(v interface{}) string { + if v == nil { + return "V" + } + + switch v.(type) { + // Serialized tags for base types + case nil: + return "V" + case bool: + return "Z" + case []bool: + return "[Z" + case byte: + return "B" + case []byte: + return "[B" + case int8: + return "B" + case []int8: + return "[B" + case int16: + return "S" + case []int16: + return "[S" + case uint16: // Equivalent to Char of Java + return "C" + case []uint16: + return "[C" + // case rune: + // return "C" + case int: + return "J" + case []int: + return "[J" + case int32: + return "I" + case []int32: + return "[I" + case int64: + return "J" + case []int64: + return "[J" + case time.Time: + return "java.util.Date" + case []time.Time: + return "[Ljava.util.Date" + case float32: + return "F" + case []float32: + return "[F" + case float64: + return "D" + case []float64: + return "[D" + case string: + return "java.lang.String" + case []string: + return "[Ljava.lang.String;" + case []hessian.Object: + return "[Ljava.lang.Object;" + case map[interface{}]interface{}: + // return "java.util.HashMap" + return "java.util.Map" + case hessian.POJOEnum: + return v.(hessian.POJOEnum).JavaClassName() + // Serialized tags for complex types + default: + t := reflect.TypeOf(v) + if reflect.Ptr == t.Kind() { + t = reflect.TypeOf(reflect.ValueOf(v).Elem()) + } + switch t.Kind() { + case reflect.Struct: + return "java.lang.Object" + case reflect.Slice, reflect.Array: + if t.Elem().Kind() == reflect.Struct { + return "[Ljava.lang.Object;" + } + // return "java.util.ArrayList" + return "java.util.List" + case reflect.Map: // Enter here, map may be map[string]int + return "java.util.Map" + default: + return "" + } + } + + // unreachable + // return "java.lang.RuntimeException" +} + +func getArgsTypeList(args []interface{}) (string, error) { + var ( + typ string + types string + ) + + for i := range args { + typ = getArgType(args[i]) + if typ == "" { + return types, perrors.Errorf("cat not get arg %#v type", args[i]) + } + if !strings.Contains(typ, ".") { + types += typ + } else if strings.Index(typ, "[") == 0 { + types += strings.Replace(typ, ".", "/", -1) + } else { + // java.util.List -> Ljava/util/List; + types += "L" + strings.Replace(typ, ".", "/", -1) + ";" + } + } + + return types, nil +} + +type DubboRequest struct { + Params interface{} + Attachments map[string]interface{} +} + +// NewRequest create a new DubboRequest +func NewRequest(params interface{}, atta map[string]interface{}) *DubboRequest { + if atta == nil { + atta = make(map[string]interface{}) + } + return &DubboRequest{ + Params: params, + Attachments: atta, + } +} + +func EnsureRequest(body interface{}) *DubboRequest { + if req, ok := body.(*DubboRequest); ok { + return req + } + return NewRequest(body, nil) +} + +func packRequest(service Service, header DubboHeader, req interface{}) ([]byte, error) { + var ( + err error + types string + byteArray []byte + pkgLen int + ) + + request := EnsureRequest(req) + + args, ok := request.Params.([]interface{}) + if !ok { + return nil, perrors.Errorf("@params is not of type: []interface{}") + } + + hb := header.Type == PackageHeartbeat + + ////////////////////////////////////////// + // byteArray + ////////////////////////////////////////// + // magic + switch header.Type { + case PackageHeartbeat: + byteArray = append(byteArray, DubboRequestHeartbeatHeader[:]...) + case PackageRequest_TwoWay: + byteArray = append(byteArray, DubboRequestHeaderBytesTwoWay[:]...) + default: + byteArray = append(byteArray, DubboRequestHeaderBytes[:]...) + } + + // serialization id, two way flag, event, request/response flag + // SerialID is id of serialization approach in java dubbo + byteArray[2] |= header.SerialID & SERIAL_MASK + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + encoder := hessian.NewEncoder() + encoder.Append(byteArray[:HEADER_LENGTH]) + + ////////////////////////////////////////// + // body + ////////////////////////////////////////// + if hb { + encoder.Encode(nil) + goto END + } + + // dubbo version + path + version + method + encoder.Encode(DEFAULT_DUBBO_PROTOCOL_VERSION) + encoder.Encode(service.Path) + encoder.Encode(service.Version) + encoder.Encode(service.Method) + + // args = args type list + args value list + if types, err = getArgsTypeList(args); err != nil { + return nil, perrors.Wrapf(err, " PackRequest(args:%+v)", args) + } + encoder.Encode(types) + for _, v := range args { + encoder.Encode(v) + } + + request.Attachments[PATH_KEY] = service.Path + request.Attachments[VERSION_KEY] = service.Version + if len(service.Group) > 0 { + request.Attachments[GROUP_KEY] = service.Group + } + if len(service.Interface) > 0 { + request.Attachments[INTERFACE_KEY] = service.Interface + } + if service.Timeout != 0 { + request.Attachments[TIMEOUT_KEY] = strconv.Itoa(int(service.Timeout / time.Millisecond)) + } + + encoder.Encode(request.Attachments) + +END: + byteArray = encoder.Buffer() + pkgLen = len(byteArray) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + // byteArray{body length} + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen-HEADER_LENGTH)) + return byteArray, nil +} + +// hessian decode request body +func unpackRequestBody(decoder *hessian.Decoder, reqObj interface{}) error { + + if decoder == nil { + return perrors.Errorf("@decoder is nil") + } + + req, ok := reqObj.([]interface{}) + if !ok { + return perrors.Errorf("@reqObj is not of type: []interface{}") + } + if len(req) < 7 { + return perrors.New("length of @reqObj should be 7") + } + + var ( + err error + dubboVersion, target, serviceVersion, method, argsTypes interface{} + args []interface{} + ) + + dubboVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[0] = dubboVersion + + target, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[1] = target + + serviceVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[2] = serviceVersion + + method, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[3] = method + + argsTypes, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[4] = argsTypes + + ats := DescRegex.FindAllString(argsTypes.(string), -1) + var arg interface{} + for i := 0; i < len(ats); i++ { + arg, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + args = append(args, arg) + } + req[5] = args + + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + v[DUBBO_VERSION_KEY] = dubboVersion + req[6] = ToMapStringInterface(v) + return nil + } + + return perrors.Errorf("get wrong attachments: %+v", attachments) +} + +func ToMapStringInterface(origin map[interface{}]interface{}) map[string]interface{} { + dest := make(map[string]interface{}, len(origin)) + for k, v := range origin { + if kv, ok := k.(string); ok { + if v == nil { + dest[kv] = "" + continue + } + dest[kv] = v + } + } + return dest +} diff --git a/protocol/dubbo/hessian2/hessian_request_test.go b/protocol/dubbo/hessian2/hessian_request_test.go new file mode 100644 index 0000000000000000000000000000000000000000..98d5f2399c9fdbf99a7274e0c3e53c2c175f774f --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_request_test.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 hessian2 + +import ( + "reflect" + "strconv" + "testing" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/stretchr/testify/assert" +) + +type TestEnumGender hessian.JavaEnum + +const ( + MAN hessian.JavaEnum = iota + WOMAN +) + +var genderName = map[hessian.JavaEnum]string{ + MAN: "MAN", + WOMAN: "WOMAN", +} + +var genderValue = map[string]hessian.JavaEnum{ + "MAN": MAN, + "WOMAN": WOMAN, +} + +func (g TestEnumGender) JavaClassName() string { + return "com.ikurento.test.TestEnumGender" +} + +func (g TestEnumGender) String() string { + s, ok := genderName[hessian.JavaEnum(g)] + if ok { + return s + } + + return strconv.Itoa(int(g)) +} + +func (g TestEnumGender) EnumValue(s string) hessian.JavaEnum { + v, ok := genderValue[s] + if ok { + return v + } + + return hessian.InvalidJavaEnum +} + +func TestPackRequest(t *testing.T) { + bytes, err := packRequest(Service{ + Path: "test", + Interface: "ITest", + Version: "v1.0", + Method: "test", + Timeout: time.Second * 10, + }, DubboHeader{ + SerialID: 0, + Type: PackageRequest, + ID: 123, + }, []interface{}{1, 2}) + + assert.Nil(t, err) + + if bytes != nil { + t.Logf("pack request: %s", string(bytes)) + } +} + +func TestGetArgsTypeList(t *testing.T) { + type Test struct{} + str, err := getArgsTypeList([]interface{}{nil, 1, []int{2}, true, []bool{false}, "a", []string{"b"}, Test{}, &Test{}, []Test{}, map[string]Test{}, TestEnumGender(MAN)}) + assert.NoError(t, err) + assert.Equal(t, "VJ[JZ[ZLjava/lang/String;[Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;Ljava/util/Map;Lcom/ikurento/test/TestEnumGender;", str) +} + +func TestDescRegex(t *testing.T) { + results := DescRegex.FindAllString("Ljava/lang/String;", -1) + assert.Equal(t, 1, len(results)) + assert.Equal(t, "Ljava/lang/String;", results[0]) + + results = DescRegex.FindAllString("Ljava/lang/String;I", -1) + assert.Equal(t, 2, len(results)) + assert.Equal(t, "Ljava/lang/String;", results[0]) + assert.Equal(t, "I", results[1]) + + results = DescRegex.FindAllString("ILjava/lang/String;", -1) + assert.Equal(t, 2, len(results)) + assert.Equal(t, "I", results[0]) + assert.Equal(t, "Ljava/lang/String;", results[1]) + + results = DescRegex.FindAllString("ILjava/lang/String;IZ", -1) + assert.Equal(t, 4, len(results)) + assert.Equal(t, "I", results[0]) + assert.Equal(t, "Ljava/lang/String;", results[1]) + assert.Equal(t, "I", results[2]) + assert.Equal(t, "Z", results[3]) + + results = DescRegex.FindAllString("[Ljava/lang/String;[I", -1) + assert.Equal(t, 2, len(results)) + assert.Equal(t, "[Ljava/lang/String;", results[0]) + assert.Equal(t, "[I", results[1]) +} + +func TestIssue192(t *testing.T) { + type args struct { + origin map[interface{}]interface{} + } + tests := []struct { + name string + args args + want map[string]interface{} + }{ + { + name: "not null", + args: args{ + origin: map[interface{}]interface{}{ + "1": nil, + "2": "3", + "": "", + }, + }, + want: map[string]interface{}{ + "1": "", + "2": "3", + "": "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToMapStringInterface(tt.args.origin); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToMapStringString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/protocol/dubbo/hessian2/hessian_response.go b/protocol/dubbo/hessian2/hessian_response.go new file mode 100644 index 0000000000000000000000000000000000000000..982960ed87e74b325687ac364c97a347efe6c38f --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_response.go @@ -0,0 +1,377 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package hessian2 + +import ( + "encoding/binary" + "math" + "reflect" + "strconv" + "strings" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/apache/dubbo-go-hessian2/java_exception" + perrors "github.com/pkg/errors" +) + +// DubboResponse dubbo response +type DubboResponse struct { + RspObj interface{} + Exception error + Attachments map[string]interface{} +} + +// NewResponse create a new DubboResponse +func NewResponse(rspObj interface{}, exception error, attachments map[string]interface{}) *DubboResponse { + if attachments == nil { + attachments = make(map[string]interface{}, 8) + } + return &DubboResponse{ + RspObj: rspObj, + Exception: exception, + Attachments: attachments, + } +} + +// EnsureResponse check body type, make sure it's a DubboResponse or package it as a DubboResponse +func EnsureResponse(body interface{}) *DubboResponse { + if res, ok := body.(*DubboResponse); ok { + return res + } + if exp, ok := body.(error); ok { + return NewResponse(nil, exp, nil) + } + return NewResponse(body, nil, nil) +} + +// https://github.com/apache/dubbo/blob/dubbo-2.7.1/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/codec/ExchangeCodec.java#L256 +// hessian encode response +func packResponse(header DubboHeader, ret interface{}) ([]byte, error) { + var ( + byteArray []byte + ) + + response := EnsureResponse(ret) + + hb := header.Type == PackageHeartbeat + + // magic + if hb { + byteArray = append(byteArray, DubboResponseHeartbeatHeader[:]...) + } else { + byteArray = append(byteArray, DubboResponseHeaderBytes[:]...) + } + // set serialID, identify serialization types, eg: fastjson->6, hessian2->2 + byteArray[2] |= header.SerialID & SERIAL_MASK + // response status + if header.ResponseStatus != 0 { + byteArray[3] = header.ResponseStatus + } + + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + // body + encoder := hessian.NewEncoder() + encoder.Append(byteArray[:HEADER_LENGTH]) + + if header.ResponseStatus == Response_OK { + if hb { + encoder.Encode(nil) + } else { + atta := isSupportResponseAttachment(response.Attachments[DUBBO_VERSION_KEY]) + + var resWithException, resValue, resNullValue int32 + if atta { + resWithException = RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS + resValue = RESPONSE_VALUE_WITH_ATTACHMENTS + resNullValue = RESPONSE_NULL_VALUE_WITH_ATTACHMENTS + } else { + resWithException = RESPONSE_WITH_EXCEPTION + resValue = RESPONSE_VALUE + resNullValue = RESPONSE_NULL_VALUE + } + + if response.Exception != nil { // throw error + encoder.Encode(resWithException) + if t, ok := response.Exception.(java_exception.Throwabler); ok { + encoder.Encode(t) + } else { + encoder.Encode(java_exception.NewThrowable(response.Exception.Error())) + } + } else { + if response.RspObj == nil { + encoder.Encode(resNullValue) + } else { + encoder.Encode(resValue) + encoder.Encode(response.RspObj) // result + } + } + + if atta { + encoder.Encode(response.Attachments) // attachments + } + } + } else { + if response.Exception != nil { // throw error + encoder.Encode(response.Exception.Error()) + } else { + encoder.Encode(response.RspObj) + } + } + + byteArray = encoder.Buffer() + byteArray = hessian.EncNull(byteArray) // if not, "java client" will throw exception "unexpected end of file" + pkgLen := len(byteArray) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + // byteArray{body length} + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen-HEADER_LENGTH)) + return byteArray, nil + +} + +// hessian decode response body +func unpackResponseBody(decoder *hessian.Decoder, resp interface{}) error { + // body + if decoder == nil { + return perrors.Errorf("@decoder is nil") + } + rspType, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + + response := EnsureResponse(resp) + + switch rspType { + case RESPONSE_WITH_EXCEPTION, RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: + expt, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + if e, ok := expt.(error); ok { + response.Exception = e + } else { + response.Exception = perrors.Errorf("got exception: %+v", expt) + } + return nil + + case RESPONSE_VALUE, RESPONSE_VALUE_WITH_ATTACHMENTS: + rsp, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + response.Attachments = ToMapStringInterface(v) + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + // If the return value is nil, + // we should consider it normal + if rsp == nil { + return nil + } + + return perrors.WithStack(ReflectResponse(rsp, response.RspObj)) + + case RESPONSE_NULL_VALUE, RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: + if rspType == RESPONSE_NULL_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + return nil + } + + return nil +} + +// CopySlice copy from inSlice to outSlice +func CopySlice(inSlice, outSlice reflect.Value) error { + if inSlice.IsNil() { + return perrors.New("@in is nil") + } + if inSlice.Kind() != reflect.Slice { + return perrors.Errorf("@in is not slice, but %v", inSlice.Kind()) + } + + for outSlice.Kind() == reflect.Ptr { + outSlice = outSlice.Elem() + } + + size := inSlice.Len() + outSlice.Set(reflect.MakeSlice(outSlice.Type(), size, size)) + + for i := 0; i < size; i++ { + inSliceValue := inSlice.Index(i) + if !inSliceValue.Type().AssignableTo(outSlice.Index(i).Type()) { + return perrors.Errorf("in element type [%s] can not assign to out element type [%s]", + inSliceValue.Type().String(), outSlice.Type().String()) + } + outSlice.Index(i).Set(inSliceValue) + } + + return nil +} + +// CopyMap copy from in map to out map +func CopyMap(inMapValue, outMapValue reflect.Value) error { + if inMapValue.IsNil() { + return perrors.New("@in is nil") + } + if !inMapValue.CanInterface() { + return perrors.New("@in's Interface can not be used.") + } + if inMapValue.Kind() != reflect.Map { + return perrors.Errorf("@in is not map, but %v", inMapValue.Kind()) + } + + outMapType := hessian.UnpackPtrType(outMapValue.Type()) + hessian.SetValue(outMapValue, reflect.MakeMap(outMapType)) + + outKeyType := outMapType.Key() + + outMapValue = hessian.UnpackPtrValue(outMapValue) + outValueType := outMapValue.Type().Elem() + + for _, inKey := range inMapValue.MapKeys() { + inValue := inMapValue.MapIndex(inKey) + + if !inKey.Type().AssignableTo(outKeyType) { + return perrors.Errorf("in Key:{type:%s, value:%#v} can not assign to out Key:{type:%s} ", + inKey.Type().String(), inKey, outKeyType.String()) + } + if !inValue.Type().AssignableTo(outValueType) { + return perrors.Errorf("in Value:{type:%s, value:%#v} can not assign to out value:{type:%s}", + inValue.Type().String(), inValue, outValueType.String()) + } + outMapValue.SetMapIndex(inKey, inValue) + } + + return nil +} + +// ReflectResponse reflect return value +// TODO response object should not be copied again to another object, it should be the exact type of the object +func ReflectResponse(in interface{}, out interface{}) error { + if in == nil { + return perrors.Errorf("@in is nil") + } + + if out == nil { + return perrors.Errorf("@out is nil") + } + if reflect.TypeOf(out).Kind() != reflect.Ptr { + return perrors.Errorf("@out should be a pointer") + } + + inValue := hessian.EnsurePackValue(in) + outValue := hessian.EnsurePackValue(out) + + outType := outValue.Type().String() + if outType == "interface {}" || outType == "*interface {}" { + hessian.SetValue(outValue, inValue) + return nil + } + + switch inValue.Type().Kind() { + case reflect.Slice, reflect.Array: + return CopySlice(inValue, outValue) + case reflect.Map: + return CopyMap(inValue, outValue) + default: + hessian.SetValue(outValue, inValue) + } + + return nil +} + +var versionInt = make(map[string]int) + +// https://github.com/apache/dubbo/blob/dubbo-2.7.1/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java#L96 +// isSupportResponseAttachment is for compatibility among some dubbo version +func isSupportResponseAttachment(ver interface{}) bool { + version, ok := ver.(string) + if !ok || len(version) == 0 { + return false + } + + v, ok := versionInt[version] + if !ok { + v = version2Int(version) + if v == -1 { + return false + } + } + + if v >= 2001000 && v <= 2060200 { // 2.0.10 ~ 2.6.2 + return false + } + return v >= LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT +} + +func version2Int(ver interface{}) int { + version, ok := ver.(string) + if !ok || len(version) == 0 { + return 0 + } + var v = 0 + varr := strings.Split(version, ".") + length := len(varr) + for key, value := range varr { + v0, err := strconv.Atoi(value) + if err != nil { + return -1 + } + v += v0 * int(math.Pow10((length-key-1)*2)) + } + if length == 3 { + return v * 100 + } + return v +} diff --git a/protocol/dubbo/hessian2/hessian_response_test.go b/protocol/dubbo/hessian2/hessian_response_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f5c84baa90b3b31b271979cb1107503facac71d9 --- /dev/null +++ b/protocol/dubbo/hessian2/hessian_response_test.go @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hessian2 + +import ( + "reflect" + "testing" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/stretchr/testify/assert" +) + +func doTestReflectResponse(t *testing.T, in interface{}, out interface{}) { + err := ReflectResponse(in, out) + if err != nil { + t.Error(err) + t.FailNow() + } + + result := hessian.UnpackPtrValue(reflect.ValueOf(out)).Interface() + + equal := reflect.DeepEqual(in, result) + if !equal { + t.Errorf("expect [%v]: %v, but got [%v]: %v", reflect.TypeOf(in), in, reflect.TypeOf(result), result) + } +} + +func TestReflectResponse(t *testing.T) { + var b bool + doTestReflectResponse(t, true, &b) + doTestReflectResponse(t, false, &b) + + var i int + doTestReflectResponse(t, 123, &i) + doTestReflectResponse(t, 234, &i) + + var i16 int16 + doTestReflectResponse(t, int16(456), &i16) + + var i64 int64 + doTestReflectResponse(t, int64(789), &i64) + + var s string + doTestReflectResponse(t, "hello world", &s) + + type rr struct { + Name string + Num int + } + + var r1 rr + doTestReflectResponse(t, rr{"dubbogo", 32}, &r1) + + // ------ map test ------- + m1 := make(map[interface{}]interface{}) + var m1r map[interface{}]interface{} + m1["hello"] = "world" + m1[1] = "go" + m1["dubbo"] = 666 + doTestReflectResponse(t, m1, &m1r) + + m2 := make(map[string]string) + var m2r map[string]string + m2["hello"] = "world" + m2["dubbo"] = "666" + doTestReflectResponse(t, m2, &m2r) + + m3 := make(map[string]rr) + var m3r map[string]rr + m3["dubbo"] = rr{"hello", 123} + m3["go"] = rr{"world", 456} + doTestReflectResponse(t, m3, &m3r) + + // ------ slice test ------- + s1 := []string{"abc", "def", "hello", "world"} + var s1r []string + doTestReflectResponse(t, s1, &s1r) + + s2 := []rr{rr{"dubbo", 666}, rr{"go", 999}} + var s2r []rr + doTestReflectResponse(t, s2, &s2r) + + s3 := []interface{}{rr{"dubbo", 666}, 123, "hello"} + var s3r []interface{} + doTestReflectResponse(t, s3, &s3r) + + // ------ interface test ------- + in1 := []interface{}{rr{"dubbo", 666}, 123, "hello"} + var inr1 *interface{} + doTestReflectResponse(t, in1, reflect.New(reflect.TypeOf(inr1).Elem()).Interface()) + + in2 := make(map[string]rr) + var inr2 map[string]rr + m3["dubbo"] = rr{"hello", 123} + m3["go"] = rr{"world", 456} + doTestReflectResponse(t, in2, &inr2) +} + +// separately test copy normal map to map[interface{}]interface{} +func TestCopyMap(t *testing.T) { + type rr struct { + Name string + Num int + } + + m3 := make(map[string]rr) + var m3r map[interface{}]interface{} + r1 := rr{"hello", 123} + r2 := rr{"world", 456} + m3["dubbo"] = r1 + m3["go"] = r2 + + err := ReflectResponse(m3, &m3r) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, 2, len(m3r)) + + rr1, ok := m3r["dubbo"] + assert.True(t, ok) + assert.True(t, reflect.DeepEqual(r1, rr1)) + + rr2, ok := m3r["go"] + assert.True(t, ok) + assert.True(t, reflect.DeepEqual(r2, rr2)) +} + +// separately test copy normal slice to []interface{} +func TestCopySlice(t *testing.T) { + type rr struct { + Name string + Num int + } + + r1 := rr{"hello", 123} + r2 := rr{"world", 456} + + s1 := []rr{r1, r2} + var s1r []interface{} + + err := ReflectResponse(s1, &s1r) + if err != nil { + t.Error(err) + t.FailNow() + } + + assert.Equal(t, 2, len(s1r)) + assert.True(t, reflect.DeepEqual(r1, s1r[0])) + assert.True(t, reflect.DeepEqual(r2, s1r[1])) +} + +func TestIsSupportResponseAttachment(t *testing.T) { + is := isSupportResponseAttachment("2.X") + assert.False(t, is) + + is = isSupportResponseAttachment("2.0.10") + assert.False(t, is) + + is = isSupportResponseAttachment("2.5.3") + assert.False(t, is) + + is = isSupportResponseAttachment("2.6.2") + assert.False(t, is) + + is = isSupportResponseAttachment("1.5.5") + assert.False(t, is) + + is = isSupportResponseAttachment("0.0.0") + assert.False(t, is) + + is = isSupportResponseAttachment("2.0.2") + assert.True(t, is) + + is = isSupportResponseAttachment("2.7.2") + assert.True(t, is) +} + +func TestVersion2Int(t *testing.T) { + v := version2Int("2.1.3") + assert.Equal(t, 2010300, v) + + v = version2Int("22.11.33") + assert.Equal(t, 22113300, v) + + v = version2Int("222.111.333") + assert.Equal(t, 223143300, v) + + v = version2Int("220.110.333") + assert.Equal(t, 221133300, v) + + v = version2Int("229.119.333") + assert.Equal(t, 230223300, v) + + v = version2Int("2222.1111.3333") + assert.Equal(t, 2233443300, v) + + v = version2Int("2.11") + assert.Equal(t, 211, v) + + v = version2Int("2.1.3.4") + assert.Equal(t, 2010304, v) + + v = version2Int("2.1.3.4.5") + assert.Equal(t, 201030405, v) + +} diff --git a/protocol/dubbo/impl/codec.go b/protocol/dubbo/impl/codec.go new file mode 100644 index 0000000000000000000000000000000000000000..17e7b57b45f2334e06b757ee07e3a9ef034c920a --- /dev/null +++ b/protocol/dubbo/impl/codec.go @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package impl + +import ( + "bufio" + "encoding/binary" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/remoting" +) + +type ProtocolCodec struct { + reader *bufio.Reader + pkgType PackageType + bodyLen int + serializer Serializer + headerRead bool +} + +func (c *ProtocolCodec) ReadHeader(header *DubboHeader) error { + var err error + if c.reader.Size() < HEADER_LENGTH { + return hessian.ErrHeaderNotEnough + } + buf, err := c.reader.Peek(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + _, err = c.reader.Discard(HEADER_LENGTH) + if err != nil { // this is impossible + return perrors.WithStack(err) + } + + //// read header + if buf[0] != MAGIC_HIGH && buf[1] != MAGIC_LOW { + return hessian.ErrIllegalPackage + } + + // Header{serialization id(5 bit), event, two way, req/response} + if header.SerialID = buf[2] & SERIAL_MASK; header.SerialID == Zero { + return perrors.Errorf("serialization ID:%v", header.SerialID) + } + + flag := buf[2] & FLAG_EVENT + if flag != Zero { + header.Type |= PackageHeartbeat + } + flag = buf[2] & FLAG_REQUEST + if flag != Zero { + header.Type |= PackageRequest + flag = buf[2] & FLAG_TWOWAY + if flag != Zero { + header.Type |= PackageRequest_TwoWay + } + } else { + header.Type |= PackageResponse + header.ResponseStatus = buf[3] + if header.ResponseStatus != Response_OK { + header.Type |= PackageResponse_Exception + } + } + + // Header{req id} + header.ID = int64(binary.BigEndian.Uint64(buf[4:])) + + // Header{body len} + header.BodyLen = int(binary.BigEndian.Uint32(buf[12:])) + if header.BodyLen < 0 { + return hessian.ErrIllegalPackage + } + + c.pkgType = header.Type + c.bodyLen = header.BodyLen + + if c.reader.Buffered() < c.bodyLen { + return hessian.ErrBodyNotEnough + } + c.headerRead = true + return perrors.WithStack(err) +} + +func (c *ProtocolCodec) EncodeHeader(p DubboPackage) []byte { + header := p.Header + bs := make([]byte, 0) + switch header.Type { + case PackageHeartbeat: + if header.ResponseStatus == Zero { + bs = append(bs, hessian.DubboRequestHeartbeatHeader[:]...) + } else { + bs = append(bs, hessian.DubboResponseHeartbeatHeader[:]...) + } + case PackageResponse: + bs = append(bs, hessian.DubboResponseHeaderBytes[:]...) + if header.ResponseStatus != 0 { + bs[3] = header.ResponseStatus + } + case PackageRequest_TwoWay: + bs = append(bs, hessian.DubboRequestHeaderBytesTwoWay[:]...) + } + bs[2] |= header.SerialID & hessian.SERIAL_MASK + binary.BigEndian.PutUint64(bs[4:], uint64(header.ID)) + return bs +} + +func (c *ProtocolCodec) Encode(p DubboPackage) ([]byte, error) { + // header + if c.serializer == nil { + return nil, perrors.New("serializer should not be nil") + } + header := p.Header + switch header.Type { + case PackageHeartbeat: + if header.ResponseStatus == Zero { + return packRequest(p, c.serializer) + } + return packResponse(p, c.serializer) + + case PackageRequest, PackageRequest_TwoWay: + return packRequest(p, c.serializer) + + case PackageResponse: + return packResponse(p, c.serializer) + + default: + return nil, perrors.Errorf("Unrecognised message type: %v", header.Type) + } +} + +func (c *ProtocolCodec) Decode(p *DubboPackage) error { + if !c.headerRead { + if err := c.ReadHeader(&p.Header); err != nil { + return err + } + } + body, err := c.reader.Peek(p.GetBodyLen()) + if err != nil { + return err + } + if p.IsResponseWithException() { + logger.Infof("response with exception: %+v", p.Header) + decoder := hessian.NewDecoder(body) + p.Body = &ResponsePayload{} + exception, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + p.Body.(*ResponsePayload).Exception = perrors.Errorf("java exception:%s", exception.(string)) + return nil + } else if p.IsHeartBeat() { + // heartbeat no need to unmarshal contents + return nil + } + if c.serializer == nil { + return perrors.New("Codec serializer is nil") + } + if p.IsResponse() { + p.Body = &ResponsePayload{ + RspObj: remoting.GetPendingResponse(remoting.SequenceType(p.Header.ID)).Reply, + } + } + return c.serializer.Unmarshal(body, p) +} + +func (c *ProtocolCodec) SetSerializer(serializer Serializer) { + c.serializer = serializer +} + +func packRequest(p DubboPackage, serializer Serializer) ([]byte, error) { + var ( + byteArray []byte + pkgLen int + ) + + header := p.Header + + ////////////////////////////////////////// + // byteArray + ////////////////////////////////////////// + // magic + switch header.Type { + case PackageHeartbeat: + byteArray = append(byteArray, DubboRequestHeartbeatHeader[:]...) + case PackageRequest_TwoWay: + byteArray = append(byteArray, DubboRequestHeaderBytesTwoWay[:]...) + default: + byteArray = append(byteArray, DubboRequestHeaderBytes[:]...) + } + + // serialization id, two way flag, event, request/response flag + // SerialID is id of serialization approach in java dubbo + byteArray[2] |= header.SerialID & SERIAL_MASK + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + ////////////////////////////////////////// + // body + ////////////////////////////////////////// + if p.IsHeartBeat() { + byteArray = append(byteArray, byte('N')) + pkgLen = 1 + } else { + body, err := serializer.Marshal(p) + if err != nil { + return nil, err + } + pkgLen = len(body) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + byteArray = append(byteArray, body...) + } + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen)) + return byteArray, nil +} + +func packResponse(p DubboPackage, serializer Serializer) ([]byte, error) { + var ( + byteArray []byte + ) + header := p.Header + hb := p.IsHeartBeat() + + // magic + if hb { + byteArray = append(byteArray, DubboResponseHeartbeatHeader[:]...) + } else { + byteArray = append(byteArray, DubboResponseHeaderBytes[:]...) + } + // set serialID, identify serialization types, eg: fastjson->6, hessian2->2 + byteArray[2] |= header.SerialID & SERIAL_MASK + // response status + if header.ResponseStatus != 0 { + byteArray[3] = header.ResponseStatus + } + + // request id + binary.BigEndian.PutUint64(byteArray[4:], uint64(header.ID)) + + // body + body, err := serializer.Marshal(p) + if err != nil { + return nil, err + } + + pkgLen := len(body) + if pkgLen > int(DEFAULT_LEN) { // 8M + return nil, perrors.Errorf("Data length %d too large, max payload %d", pkgLen, DEFAULT_LEN) + } + // byteArray{body length} + binary.BigEndian.PutUint32(byteArray[12:], uint32(pkgLen)) + byteArray = append(byteArray, body...) + return byteArray, nil +} + +func NewDubboCodec(reader *bufio.Reader) *ProtocolCodec { + s, _ := GetSerializerById(constant.S_Hessian2) + return &ProtocolCodec{ + reader: reader, + pkgType: 0, + bodyLen: 0, + headerRead: false, + serializer: s.(Serializer), + } +} diff --git a/protocol/dubbo/codec_test.go b/protocol/dubbo/impl/codec_test.go similarity index 51% rename from protocol/dubbo/codec_test.go rename to protocol/dubbo/impl/codec_test.go index c2ca443637e23101679770e464f49e0cbdeab2a9..1c379282383c90ef27628af19ddaaafb2a4db9a6 100644 --- a/protocol/dubbo/codec_test.go +++ b/protocol/dubbo/impl/codec_test.go @@ -15,42 +15,46 @@ * limitations under the License. */ -package dubbo +package impl import ( - "bytes" "testing" "time" ) import ( - hessian "github.com/apache/dubbo-go-hessian2" - perrors "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) -func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { - pkg := &DubboPackage{} +import ( + "github.com/apache/dubbo-go/common/constant" +) + +func TestDubboPackage_MarshalAndUnmarshal(t *testing.T) { + pkg := NewDubboPackage(nil) pkg.Body = []interface{}{"a"} - pkg.Header.Type = hessian.PackageHeartbeat - pkg.Header.SerialID = byte(S_Dubbo) + pkg.Header.Type = PackageHeartbeat + pkg.Header.SerialID = constant.S_Hessian2 pkg.Header.ID = 10086 + pkg.SetSerializer(HessianSerializer{}) // heartbeat data, err := pkg.Marshal() assert.NoError(t, err) - pkgres := &DubboPackage{} + pkgres := NewDubboPackage(data) + pkgres.SetSerializer(HessianSerializer{}) + pkgres.Body = []interface{}{} - err = pkgres.Unmarshal(data) + err = pkgres.Unmarshal() assert.NoError(t, err) - assert.Equal(t, hessian.PackageHeartbeat|hessian.PackageRequest|hessian.PackageRequest_TwoWay, pkgres.Header.Type) - assert.Equal(t, byte(S_Dubbo), pkgres.Header.SerialID) + assert.Equal(t, PackageHeartbeat|PackageRequest|PackageRequest_TwoWay, pkgres.Header.Type) + assert.Equal(t, constant.S_Hessian2, pkgres.Header.SerialID) assert.Equal(t, int64(10086), pkgres.Header.ID) assert.Equal(t, 0, len(pkgres.Body.([]interface{}))) // request - pkg.Header.Type = hessian.PackageRequest + pkg.Header.Type = PackageRequest pkg.Service.Interface = "Service" pkg.Service.Path = "path" pkg.Service.Version = "2.6" @@ -59,25 +63,27 @@ func TestDubboPackageMarshalAndUnmarshal(t *testing.T) { data, err = pkg.Marshal() assert.NoError(t, err) - pkgres = &DubboPackage{} + pkgres = NewDubboPackage(data) + pkgres.SetSerializer(HessianSerializer{}) pkgres.Body = make([]interface{}, 7) - err = pkgres.Unmarshal(data) + err = pkgres.Unmarshal() + reassembleBody := pkgres.GetBody().(map[string]interface{}) assert.NoError(t, err) - assert.Equal(t, hessian.PackageRequest, pkgres.Header.Type) - assert.Equal(t, byte(S_Dubbo), pkgres.Header.SerialID) + assert.Equal(t, PackageRequest, pkgres.Header.Type) + assert.Equal(t, constant.S_Hessian2, pkgres.Header.SerialID) assert.Equal(t, int64(10086), pkgres.Header.ID) - assert.Equal(t, "2.0.2", pkgres.Body.([]interface{})[0]) - assert.Equal(t, "path", pkgres.Body.([]interface{})[1]) - assert.Equal(t, "2.6", pkgres.Body.([]interface{})[2]) - assert.Equal(t, "Method", pkgres.Body.([]interface{})[3]) - assert.Equal(t, "Ljava/lang/String;", pkgres.Body.([]interface{})[4]) - assert.Equal(t, []interface{}{"a"}, pkgres.Body.([]interface{})[5]) - assert.Equal(t, map[string]string{"dubbo": "2.0.2", "interface": "Service", "path": "path", "timeout": "1000", "version": "2.6"}, pkgres.Body.([]interface{})[6]) -} - -func TestIssue380(t *testing.T) { - pkg := &DubboPackage{} - buf := bytes.NewBuffer([]byte("hello")) - err := pkg.Unmarshal(buf) - assert.True(t, perrors.Cause(err) == hessian.ErrHeaderNotEnough) + assert.Equal(t, "2.0.2", reassembleBody["dubboVersion"].(string)) + assert.Equal(t, "path", pkgres.Service.Path) + assert.Equal(t, "2.6", pkgres.Service.Version) + assert.Equal(t, "Method", pkgres.Service.Method) + assert.Equal(t, "Ljava/lang/String;", reassembleBody["argsTypes"].(string)) + assert.Equal(t, []interface{}{"a"}, reassembleBody["args"]) + tmpData := map[string]interface{}{ + "dubbo": "2.0.2", + "interface": "Service", + "path": "path", + "timeout": "1000", + "version": "2.6", + } + assert.Equal(t, tmpData, reassembleBody["attachments"]) } diff --git a/protocol/dubbo/impl/const.go b/protocol/dubbo/impl/const.go new file mode 100644 index 0000000000000000000000000000000000000000..70d8bae6cad63d436f5d9f1ef69c397ee8a052f3 --- /dev/null +++ b/protocol/dubbo/impl/const.go @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package impl + +import ( + "reflect" + "regexp" + + "github.com/pkg/errors" +) + +const ( + DUBBO = "dubbo" +) + +const ( + mask = byte(127) + flag = byte(128) +) + +const ( + // Zero : byte zero + Zero = byte(0x00) +) + +// constansts +const ( + TAG_READ = int32(-1) + ASCII_GAP = 32 + CHUNK_SIZE = 4096 + BC_BINARY = byte('B') // final chunk + BC_BINARY_CHUNK = byte('A') // non-final chunk + + BC_BINARY_DIRECT = byte(0x20) // 1-byte length binary + BINARY_DIRECT_MAX = byte(0x0f) + BC_BINARY_SHORT = byte(0x34) // 2-byte length binary + BINARY_SHORT_MAX = 0x3ff // 0-1023 binary + + BC_DATE = byte(0x4a) // 64-bit millisecond UTC date + BC_DATE_MINUTE = byte(0x4b) // 32-bit minute UTC date + + BC_DOUBLE = byte('D') // IEEE 64-bit double + + BC_DOUBLE_ZERO = byte(0x5b) + BC_DOUBLE_ONE = byte(0x5c) + BC_DOUBLE_BYTE = byte(0x5d) + BC_DOUBLE_SHORT = byte(0x5e) + BC_DOUBLE_MILL = byte(0x5f) + + BC_FALSE = byte('F') // boolean false + + BC_INT = byte('I') // 32-bit int + + INT_DIRECT_MIN = -0x10 + INT_DIRECT_MAX = byte(0x2f) + BC_INT_ZERO = byte(0x90) + + INT_BYTE_MIN = -0x800 + INT_BYTE_MAX = 0x7ff + BC_INT_BYTE_ZERO = byte(0xc8) + + BC_END = byte('Z') + + INT_SHORT_MIN = -0x40000 + INT_SHORT_MAX = 0x3ffff + BC_INT_SHORT_ZERO = byte(0xd4) + + BC_LIST_VARIABLE = byte(0x55) + BC_LIST_FIXED = byte('V') + BC_LIST_VARIABLE_UNTYPED = byte(0x57) + BC_LIST_FIXED_UNTYPED = byte(0x58) + _listFixedTypedLenTagMin = byte(0x70) + _listFixedTypedLenTagMax = byte(0x77) + _listFixedUntypedLenTagMin = byte(0x78) + _listFixedUntypedLenTagMax = byte(0x7f) + + BC_LIST_DIRECT = byte(0x70) + BC_LIST_DIRECT_UNTYPED = byte(0x78) + LIST_DIRECT_MAX = byte(0x7) + + BC_LONG = byte('L') // 64-bit signed integer + LONG_DIRECT_MIN = -0x08 + LONG_DIRECT_MAX = byte(0x0f) + BC_LONG_ZERO = byte(0xe0) + + LONG_BYTE_MIN = -0x800 + LONG_BYTE_MAX = 0x7ff + BC_LONG_BYTE_ZERO = byte(0xf8) + + LONG_SHORT_MIN = -0x40000 + LONG_SHORT_MAX = 0x3ffff + BC_LONG_SHORT_ZERO = byte(0x3c) + + BC_LONG_INT = byte(0x59) + + BC_MAP = byte('M') + BC_MAP_UNTYPED = byte('H') + + BC_NULL = byte('N') // x4e + + BC_OBJECT = byte('O') + BC_OBJECT_DEF = byte('C') + + BC_OBJECT_DIRECT = byte(0x60) + OBJECT_DIRECT_MAX = byte(0x0f) + + BC_REF = byte(0x51) + + BC_STRING = byte('S') // final string + BC_STRING_CHUNK = byte('R') // non-final string + + BC_STRING_DIRECT = byte(0x00) + STRING_DIRECT_MAX = byte(0x1f) + BC_STRING_SHORT = byte(0x30) + STRING_SHORT_MAX = 0x3ff + + BC_TRUE = byte('T') + + P_PACKET_CHUNK = byte(0x4f) + P_PACKET = byte('P') + + P_PACKET_DIRECT = byte(0x80) + PACKET_DIRECT_MAX = byte(0x7f) + + P_PACKET_SHORT = byte(0x70) + PACKET_SHORT_MAX = 0xfff + ARRAY_STRING = "[string" + ARRAY_INT = "[int" + ARRAY_DOUBLE = "[double" + ARRAY_FLOAT = "[float" + ARRAY_BOOL = "[boolean" + ARRAY_LONG = "[long" + + PATH_KEY = "path" + GROUP_KEY = "group" + INTERFACE_KEY = "interface" + VERSION_KEY = "version" + TIMEOUT_KEY = "timeout" + + STRING_NIL = "" + STRING_TRUE = "true" + STRING_FALSE = "false" + STRING_ZERO = "0.0" + STRING_ONE = "1.0" +) + +// ResponsePayload related consts +const ( + Response_OK byte = 20 + Response_CLIENT_TIMEOUT byte = 30 + Response_SERVER_TIMEOUT byte = 31 + Response_BAD_REQUEST byte = 40 + Response_BAD_RESPONSE byte = 50 + Response_SERVICE_NOT_FOUND byte = 60 + Response_SERVICE_ERROR byte = 70 + Response_SERVER_ERROR byte = 80 + Response_CLIENT_ERROR byte = 90 + + // According to "java dubbo" There are two cases of response: + // 1. with attachments + // 2. no attachments + RESPONSE_WITH_EXCEPTION int32 = 0 + RESPONSE_VALUE int32 = 1 + RESPONSE_NULL_VALUE int32 = 2 + RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS int32 = 3 + RESPONSE_VALUE_WITH_ATTACHMENTS int32 = 4 + RESPONSE_NULL_VALUE_WITH_ATTACHMENTS int32 = 5 +) + +/** + * the dubbo protocol header length is 16 Bytes. + * the first 2 Bytes is magic code '0xdabb' + * the next 1 Byte is message flags, in which its 16-20 bit is serial id, 21 for event, 22 for two way, 23 for request/response flag + * the next 1 Bytes is response state. + * the next 8 Bytes is package DI. + * the next 4 Bytes is package length. + **/ +const ( + // header length. + HEADER_LENGTH = 16 + + // magic header + MAGIC = uint16(0xdabb) + MAGIC_HIGH = byte(0xda) + MAGIC_LOW = byte(0xbb) + + // message flag. + FLAG_REQUEST = byte(0x80) + FLAG_TWOWAY = byte(0x40) + FLAG_EVENT = byte(0x20) // for heartbeat + SERIAL_MASK = 0x1f + + DUBBO_VERSION = "2.5.4" + DUBBO_VERSION_KEY = "dubbo" + DEFAULT_DUBBO_PROTOCOL_VERSION = "2.0.2" // Dubbo RPC protocol version, for compatibility, it must not be between 2.0.10 ~ 2.6.2 + LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT = 2000200 + DEFAULT_LEN = 8388608 // 8 * 1024 * 1024 default body max length +) + +// regular +const ( + JAVA_IDENT_REGEX = "(?:[_$a-zA-Z][_$a-zA-Z0-9]*)" + CLASS_DESC = "(?:L" + JAVA_IDENT_REGEX + "(?:\\/" + JAVA_IDENT_REGEX + ")*;)" + ARRAY_DESC = "(?:\\[+(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "))" + DESC_REGEX = "(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "|" + ARRAY_DESC + ")" +) + +// Dubbo request response related consts +var ( + DubboRequestHeaderBytesTwoWay = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY} + DubboRequestHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST} + DubboResponseHeaderBytes = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, Zero, Response_OK} + DubboRequestHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_REQUEST | FLAG_TWOWAY | FLAG_EVENT} + DubboResponseHeartbeatHeader = [HEADER_LENGTH]byte{MAGIC_HIGH, MAGIC_LOW, FLAG_EVENT} +) + +// Error part +var ( + ErrHeaderNotEnough = errors.New("header buffer too short") + ErrBodyNotEnough = errors.New("body buffer too short") + ErrJavaException = errors.New("got java exception") + ErrIllegalPackage = errors.New("illegal package!") +) + +// DescRegex ... +var DescRegex, _ = regexp.Compile(DESC_REGEX) + +var NilValue = reflect.Zero(reflect.TypeOf((*interface{})(nil)).Elem()) + +// Body map keys +var ( + DubboVersionKey = "dubboVersion" + ArgsTypesKey = "argsTypes" + ArgsKey = "args" + ServiceKey = "service" + AttachmentsKey = "attachments" +) diff --git a/protocol/dubbo/impl/hessian.go b/protocol/dubbo/impl/hessian.go new file mode 100644 index 0000000000000000000000000000000000000000..5fa1f2ece337e268c4907465bdc69ced76641ce7 --- /dev/null +++ b/protocol/dubbo/impl/hessian.go @@ -0,0 +1,528 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package impl + +import ( + "math" + "reflect" + "strconv" + "strings" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + "github.com/apache/dubbo-go-hessian2/java_exception" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" +) + +type HessianSerializer struct { +} + +func (h HessianSerializer) Marshal(p DubboPackage) ([]byte, error) { + encoder := hessian.NewEncoder() + if p.IsRequest() { + return marshalRequest(encoder, p) + } + return marshalResponse(encoder, p) +} + +func (h HessianSerializer) Unmarshal(input []byte, p *DubboPackage) error { + if p.IsHeartBeat() { + return nil + } + if p.IsRequest() { + return unmarshalRequestBody(input, p) + } + return unmarshalResponseBody(input, p) +} + +func marshalResponse(encoder *hessian.Encoder, p DubboPackage) ([]byte, error) { + header := p.Header + response := EnsureResponsePayload(p.Body) + if header.ResponseStatus == Response_OK { + if p.IsHeartBeat() { + encoder.Encode(nil) + } else { + var version string + if attachmentVersion, ok := response.Attachments[DUBBO_VERSION_KEY]; ok { + version = attachmentVersion.(string) + } + atta := isSupportResponseAttachment(version) + + var resWithException, resValue, resNullValue int32 + if atta { + resWithException = RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS + resValue = RESPONSE_VALUE_WITH_ATTACHMENTS + resNullValue = RESPONSE_NULL_VALUE_WITH_ATTACHMENTS + } else { + resWithException = RESPONSE_WITH_EXCEPTION + resValue = RESPONSE_VALUE + resNullValue = RESPONSE_NULL_VALUE + } + + if response.Exception != nil { // throw error + encoder.Encode(resWithException) + if t, ok := response.Exception.(java_exception.Throwabler); ok { + encoder.Encode(t) + } else { + encoder.Encode(java_exception.NewThrowable(response.Exception.Error())) + } + } else { + if response.RspObj == nil { + encoder.Encode(resNullValue) + } else { + encoder.Encode(resValue) + encoder.Encode(response.RspObj) // result + } + } + + if atta { + encoder.Encode(response.Attachments) // attachments + } + } + } else { + if response.Exception != nil { // throw error + encoder.Encode(response.Exception.Error()) + } else { + encoder.Encode(response.RspObj) + } + } + bs := encoder.Buffer() + // encNull + bs = append(bs, byte('N')) + return bs, nil +} + +func marshalRequest(encoder *hessian.Encoder, p DubboPackage) ([]byte, error) { + service := p.Service + request := EnsureRequestPayload(p.Body) + encoder.Encode(DEFAULT_DUBBO_PROTOCOL_VERSION) + encoder.Encode(service.Path) + encoder.Encode(service.Version) + encoder.Encode(service.Method) + + args, ok := request.Params.([]interface{}) + + if !ok { + logger.Infof("request args are: %+v", request.Params) + return nil, perrors.Errorf("@params is not of type: []interface{}") + } + types, err := getArgsTypeList(args) + if err != nil { + return nil, perrors.Wrapf(err, " PackRequest(args:%+v)", args) + } + encoder.Encode(types) + for _, v := range args { + encoder.Encode(v) + } + + request.Attachments[PATH_KEY] = service.Path + request.Attachments[VERSION_KEY] = service.Version + if len(service.Group) > 0 { + request.Attachments[GROUP_KEY] = service.Group + } + if len(service.Interface) > 0 { + request.Attachments[INTERFACE_KEY] = service.Interface + } + if service.Timeout != 0 { + request.Attachments[TIMEOUT_KEY] = strconv.Itoa(int(service.Timeout / time.Millisecond)) + } + + encoder.Encode(request.Attachments) + return encoder.Buffer(), nil + +} + +var versionInt = make(map[string]int) + +// https://github.com/apache/dubbo/blob/dubbo-2.7.1/dubbo-common/src/main/java/org/apache/dubbo/common/Version.java#L96 +// isSupportResponseAttachment is for compatibility among some dubbo version +func isSupportResponseAttachment(version string) bool { + if version == "" { + return false + } + + v, ok := versionInt[version] + if !ok { + v = version2Int(version) + if v == -1 { + return false + } + } + + if v >= 2001000 && v <= 2060200 { // 2.0.10 ~ 2.6.2 + return false + } + return v >= LOWEST_VERSION_FOR_RESPONSE_ATTACHMENT +} + +func version2Int(version string) int { + var v = 0 + varr := strings.Split(version, ".") + length := len(varr) + for key, value := range varr { + v0, err := strconv.Atoi(value) + if err != nil { + return -1 + } + v += v0 * int(math.Pow10((length-key-1)*2)) + } + if length == 3 { + return v * 100 + } + return v +} + +func unmarshalRequestBody(body []byte, p *DubboPackage) error { + if p.Body == nil { + p.SetBody(make([]interface{}, 7)) + } + decoder := hessian.NewDecoder(body) + var ( + err error + dubboVersion, target, serviceVersion, method, argsTypes interface{} + args []interface{} + ) + req, ok := p.Body.([]interface{}) + if !ok { + return perrors.Errorf("@reqObj is not of type: []interface{}") + } + dubboVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[0] = dubboVersion + + target, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[1] = target + + serviceVersion, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[2] = serviceVersion + + method, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[3] = method + + argsTypes, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + req[4] = argsTypes + + ats := hessian.DescRegex.FindAllString(argsTypes.(string), -1) + var arg interface{} + for i := 0; i < len(ats); i++ { + arg, err = decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + args = append(args, arg) + } + req[5] = args + + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + + if v, ok := attachments.(map[interface{}]interface{}); ok { + v[DUBBO_VERSION_KEY] = dubboVersion + req[6] = ToMapStringInterface(v) + buildServerSidePackageBody(p) + return nil + } + return perrors.Errorf("get wrong attachments: %+v", attachments) +} + +func unmarshalResponseBody(body []byte, p *DubboPackage) error { + decoder := hessian.NewDecoder(body) + rspType, err := decoder.Decode() + if p.Body == nil { + p.SetBody(&ResponsePayload{}) + } + if err != nil { + return perrors.WithStack(err) + } + response := EnsureResponsePayload(p.Body) + + switch rspType { + case RESPONSE_WITH_EXCEPTION, RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: + expt, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + if e, ok := expt.(error); ok { + response.Exception = e + } else { + response.Exception = perrors.Errorf("got exception: %+v", expt) + } + return nil + + case RESPONSE_VALUE, RESPONSE_VALUE_WITH_ATTACHMENTS: + rsp, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if rspType == RESPONSE_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + + return perrors.WithStack(hessian.ReflectResponse(rsp, response.RspObj)) + + case RESPONSE_NULL_VALUE, RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: + if rspType == RESPONSE_NULL_VALUE_WITH_ATTACHMENTS { + attachments, err := decoder.Decode() + if err != nil { + return perrors.WithStack(err) + } + if v, ok := attachments.(map[interface{}]interface{}); ok { + atta := ToMapStringInterface(v) + response.Attachments = atta + } else { + return perrors.Errorf("get wrong attachments: %+v", attachments) + } + } + return nil + } + return nil +} + +func buildServerSidePackageBody(pkg *DubboPackage) { + req := pkg.GetBody().([]interface{}) // length of body should be 7 + if len(req) > 0 { + var dubboVersion, argsTypes string + var args []interface{} + var attachments map[string]interface{} + svc := Service{} + if req[0] != nil { + dubboVersion = req[0].(string) + } + if req[1] != nil { + svc.Path = req[1].(string) + } + if req[2] != nil { + svc.Version = req[2].(string) + } + if req[3] != nil { + svc.Method = req[3].(string) + } + if req[4] != nil { + argsTypes = req[4].(string) + } + if req[5] != nil { + args = req[5].([]interface{}) + } + if req[6] != nil { + attachments = req[6].(map[string]interface{}) + } + if svc.Path == "" && attachments[constant.PATH_KEY] != nil && len(attachments[constant.PATH_KEY].(string)) > 0 { + svc.Path = attachments[constant.PATH_KEY].(string) + } + if _, ok := attachments[constant.INTERFACE_KEY]; ok { + svc.Interface = attachments[constant.INTERFACE_KEY].(string) + } else { + svc.Interface = svc.Path + } + if _, ok := attachments[constant.GROUP_KEY]; ok { + svc.Group = attachments[constant.GROUP_KEY].(string) + } + pkg.SetService(svc) + pkg.SetBody(map[string]interface{}{ + "dubboVersion": dubboVersion, + "argsTypes": argsTypes, + "args": args, + "service": common.ServiceMap.GetService(DUBBO, svc.Interface, svc.Group, svc.Version), // path as a key + "attachments": attachments, + }) + } +} + +func getArgsTypeList(args []interface{}) (string, error) { + var ( + typ string + types string + ) + + for i := range args { + typ = getArgType(args[i]) + if typ == "" { + return types, perrors.Errorf("cat not get arg %#v type", args[i]) + } + if !strings.Contains(typ, ".") { + types += typ + } else if strings.Index(typ, "[") == 0 { + types += strings.Replace(typ, ".", "/", -1) + } else { + // java.util.List -> Ljava/util/List; + types += "L" + strings.Replace(typ, ".", "/", -1) + ";" + } + } + + return types, nil +} + +func getArgType(v interface{}) string { + if v == nil { + return "V" + } + + switch v.(type) { + // Serialized tags for base types + case nil: + return "V" + case bool: + return "Z" + case []bool: + return "[Z" + case byte: + return "B" + case []byte: + return "[B" + case int8: + return "B" + case []int8: + return "[B" + case int16: + return "S" + case []int16: + return "[S" + case uint16: // Equivalent to Char of Java + return "C" + case []uint16: + return "[C" + // case rune: + // return "C" + case int: + return "J" + case []int: + return "[J" + case int32: + return "I" + case []int32: + return "[I" + case int64: + return "J" + case []int64: + return "[J" + case time.Time: + return "java.util.Date" + case []time.Time: + return "[Ljava.util.Date" + case float32: + return "F" + case []float32: + return "[F" + case float64: + return "D" + case []float64: + return "[D" + case string: + return "java.lang.String" + case []string: + return "[Ljava.lang.String;" + case []hessian.Object: + return "[Ljava.lang.Object;" + case map[interface{}]interface{}: + // return "java.util.HashMap" + return "java.util.Map" + case hessian.POJOEnum: + return v.(hessian.POJOEnum).JavaClassName() + // Serialized tags for complex types + default: + t := reflect.TypeOf(v) + if reflect.Ptr == t.Kind() { + t = reflect.TypeOf(reflect.ValueOf(v).Elem()) + } + switch t.Kind() { + case reflect.Struct: + v, ok := v.(hessian.POJO) + if ok { + return v.JavaClassName() + } + return "java.lang.Object" + case reflect.Slice, reflect.Array: + if t.Elem().Kind() == reflect.Struct { + return "[Ljava.lang.Object;" + } + // return "java.util.ArrayList" + return "java.util.List" + case reflect.Map: // Enter here, map may be map[string]int + return "java.util.Map" + default: + return "" + } + } + + // unreachable + // return "java.lang.RuntimeException" +} + +func ToMapStringInterface(origin map[interface{}]interface{}) map[string]interface{} { + dest := make(map[string]interface{}, len(origin)) + for k, v := range origin { + if kv, ok := k.(string); ok { + if v == nil { + dest[kv] = "" + continue + } + dest[kv] = v + } + } + return dest +} + +func init() { + SetSerializer("hessian2", HessianSerializer{}) +} diff --git a/protocol/dubbo/impl/package.go b/protocol/dubbo/impl/package.go new file mode 100644 index 0000000000000000000000000000000000000000..6f6d2ea9753a513412a4f5099c396dd90cf454ba --- /dev/null +++ b/protocol/dubbo/impl/package.go @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package impl + +import ( + "bufio" + "bytes" + "fmt" + "time" +) + +import ( + "github.com/pkg/errors" +) + +type PackageType int + +// enum part +const ( + PackageError = PackageType(0x01) + PackageRequest = PackageType(0x02) + PackageResponse = PackageType(0x04) + PackageHeartbeat = PackageType(0x08) + PackageRequest_TwoWay = PackageType(0x10) + PackageResponse_Exception = PackageType(0x20) + PackageType_BitSize = 0x2f +) + +type DubboHeader struct { + SerialID byte + Type PackageType + ID int64 + BodyLen int + ResponseStatus byte +} + +// Service defines service instance +type Service struct { + Path string + Interface string + Group string + Version string + Method string + Timeout time.Duration // request timeout +} + +type DubboPackage struct { + Header DubboHeader + Service Service + Body interface{} + Err error + Codec *ProtocolCodec +} + +func (p DubboPackage) String() string { + return fmt.Sprintf("HessianPackage: Header-%v, Path-%v, Body-%v", p.Header, p.Service, p.Body) +} + +func (p *DubboPackage) ReadHeader() error { + return p.Codec.ReadHeader(&p.Header) +} + +func (p *DubboPackage) Marshal() (*bytes.Buffer, error) { + if p.Codec == nil { + return nil, errors.New("Codec is nil") + } + pkg, err := p.Codec.Encode(*p) + if err != nil { + return nil, errors.WithStack(err) + } + return bytes.NewBuffer(pkg), nil +} + +func (p *DubboPackage) Unmarshal() error { + if p.Codec == nil { + return errors.New("Codec is nil") + } + return p.Codec.Decode(p) +} + +func (p DubboPackage) IsHeartBeat() bool { + return p.Header.Type&PackageHeartbeat != 0 +} + +func (p DubboPackage) IsRequest() bool { + return p.Header.Type&(PackageRequest_TwoWay|PackageRequest) != 0 +} + +func (p DubboPackage) IsResponse() bool { + return p.Header.Type == PackageResponse +} + +func (p DubboPackage) IsResponseWithException() bool { + flag := PackageResponse | PackageResponse_Exception + return p.Header.Type&flag == flag +} + +func (p DubboPackage) GetBodyLen() int { + return p.Header.BodyLen +} + +func (p DubboPackage) GetLen() int { + return HEADER_LENGTH + p.Header.BodyLen +} + +func (p DubboPackage) GetBody() interface{} { + return p.Body +} + +func (p *DubboPackage) SetBody(body interface{}) { + p.Body = body +} + +func (p *DubboPackage) SetHeader(header DubboHeader) { + p.Header = header +} + +func (p *DubboPackage) SetService(svc Service) { + p.Service = svc +} + +func (p *DubboPackage) SetID(id int64) { + p.Header.ID = id +} + +func (p DubboPackage) GetHeader() DubboHeader { + return p.Header +} + +func (p DubboPackage) GetService() Service { + return p.Service +} + +func (p *DubboPackage) SetResponseStatus(status byte) { + p.Header.ResponseStatus = status +} + +func (p *DubboPackage) SetSerializer(serializer Serializer) { + p.Codec.SetSerializer(serializer) +} + +func NewDubboPackage(data *bytes.Buffer) *DubboPackage { + var codec *ProtocolCodec + if data == nil { + codec = NewDubboCodec(nil) + } else { + codec = NewDubboCodec(bufio.NewReaderSize(data, len(data.Bytes()))) + } + return &DubboPackage{ + Header: DubboHeader{}, + Service: Service{}, + Body: nil, + Err: nil, + Codec: codec, + } +} diff --git a/protocol/dubbo/impl/request.go b/protocol/dubbo/impl/request.go new file mode 100644 index 0000000000000000000000000000000000000000..ef520083e6457f0ceaf3e80778f79d3e0ce686ab --- /dev/null +++ b/protocol/dubbo/impl/request.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 impl + +type RequestPayload struct { + Params interface{} + Attachments map[string]interface{} +} + +func NewRequestPayload(args interface{}, atta map[string]interface{}) *RequestPayload { + if atta == nil { + atta = make(map[string]interface{}) + } + return &RequestPayload{ + Params: args, + Attachments: atta, + } +} + +func EnsureRequestPayload(body interface{}) *RequestPayload { + if req, ok := body.(*RequestPayload); ok { + return req + } + return NewRequestPayload(body, nil) +} diff --git a/protocol/dubbo/impl/response.go b/protocol/dubbo/impl/response.go new file mode 100644 index 0000000000000000000000000000000000000000..9fde1eb249c40e546bdabc54d15e4a3b6d1ea399 --- /dev/null +++ b/protocol/dubbo/impl/response.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 impl + +type ResponsePayload struct { + RspObj interface{} + Exception error + Attachments map[string]interface{} +} + +// NewResponse create a new ResponsePayload +func NewResponsePayload(rspObj interface{}, exception error, attachments map[string]interface{}) *ResponsePayload { + if attachments == nil { + attachments = make(map[string]interface{}) + } + return &ResponsePayload{ + RspObj: rspObj, + Exception: exception, + Attachments: attachments, + } +} + +func EnsureResponsePayload(body interface{}) *ResponsePayload { + if res, ok := body.(*ResponsePayload); ok { + return res + } + if exp, ok := body.(error); ok { + return NewResponsePayload(nil, exp, nil) + } + return NewResponsePayload(body, nil, nil) +} diff --git a/protocol/dubbo/impl/serialization.go b/protocol/dubbo/impl/serialization.go new file mode 100644 index 0000000000000000000000000000000000000000..7ce76a87c17376cfb17983b6b6b3bdaf2771c22e --- /dev/null +++ b/protocol/dubbo/impl/serialization.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 impl + +import ( + "fmt" +) + +import ( + "github.com/apache/dubbo-go/common/constant" +) + +var ( + serializers = make(map[string]interface{}) + nameMaps = make(map[byte]string) +) + +func init() { + nameMaps = map[byte]string{ + constant.S_Hessian2: constant.HESSIAN2_SERIALIZATION, + constant.S_Proto: constant.PROTOBUF_SERIALIZATION, + } +} + +func SetSerializer(name string, serializer interface{}) { + serializers[name] = serializer +} + +func GetSerializerById(id byte) (interface{}, error) { + name, ok := nameMaps[id] + if !ok { + panic(fmt.Sprintf("serialId %d not found", id)) + } + serializer, ok := serializers[name] + if !ok { + panic(fmt.Sprintf("serialization %s not found", name)) + } + return serializer, nil +} diff --git a/protocol/dubbo/impl/serialize.go b/protocol/dubbo/impl/serialize.go new file mode 100644 index 0000000000000000000000000000000000000000..1f913f7caae2a2c758764a92e4e63a0d9555ff1a --- /dev/null +++ b/protocol/dubbo/impl/serialize.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 impl + +import ( + "github.com/apache/dubbo-go/common/constant" +) + +type Serializer interface { + Marshal(p DubboPackage) ([]byte, error) + Unmarshal([]byte, *DubboPackage) error +} + +func LoadSerializer(p *DubboPackage) error { + // NOTE: default serialID is S_Hessian + serialID := p.Header.SerialID + if serialID == 0 { + serialID = constant.S_Hessian2 + } + serializer, err := GetSerializerById(serialID) + if err != nil { + panic(err) + } + p.SetSerializer(serializer.(Serializer)) + return nil +} diff --git a/protocol/dubbo/listener.go b/protocol/dubbo/listener.go deleted file mode 100644 index a17b282fdf3a47cc739793eb93079f758d8de205..0000000000000000000000000000000000000000 --- a/protocol/dubbo/listener.go +++ /dev/null @@ -1,370 +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 dubbo - -import ( - "context" - "fmt" - "net/url" - "sync" - "sync/atomic" - "time" -) - -import ( - "github.com/apache/dubbo-getty" - "github.com/apache/dubbo-go-hessian2" - "github.com/opentracing/opentracing-go" - perrors "github.com/pkg/errors" -) - -import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/common/logger" - "github.com/apache/dubbo-go/protocol" - "github.com/apache/dubbo-go/protocol/invocation" -) - -// todo: writePkg_Timeout will entry *.yml -const ( - writePkg_Timeout = 5 * time.Second -) - -var ( - errTooManySessions = perrors.New("too many sessions") -) - -type rpcSession struct { - session getty.Session - 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) -} - -// ////////////////////////////////////////// -// RpcClientHandler -// ////////////////////////////////////////// - -// RpcClientHandler is handler of RPC Client -type RpcClientHandler struct { - conn *gettyRPCClient -} - -// NewRpcClientHandler creates RpcClientHandler with @gettyRPCClient -func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler { - return &RpcClientHandler{conn: client} -} - -// OnOpen notified when RPC client session opened -func (h *RpcClientHandler) OnOpen(session getty.Session) error { - h.conn.addSession(session) - return nil -} - -// OnError notified when RPC client session got any error -func (h *RpcClientHandler) OnError(session getty.Session, err error) { - logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err) - h.conn.removeSession(session) -} - -// 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 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 { - logger.Errorf("illegal package") - return - } - - if p.Header.Type&hessian.PackageHeartbeat != 0x00 { - if p.Header.Type&hessian.PackageResponse != 0x00 { - logger.Debugf("get rpc heartbeat response{header: %#v, body: %#v}", p.Header, p.Body) - if p.Err != nil { - logger.Errorf("rpc heartbeat response{error: %#v}", p.Err) - } - h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) - } else { - logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) - p.Header.ResponseStatus = hessian.Response_OK - reply(session, p, hessian.PackageHeartbeat) - } - return - } - logger.Debugf("get rpc response{header: %#v, body: %#v}", p.Header, p.Body) - - h.conn.updateSession(session) - - pendingResponse := h.conn.pool.rpcClient.removePendingResponse(SequenceType(p.Header.ID)) - if pendingResponse == nil { - logger.Errorf("failed to get pending response context for response package %s", *p) - return - } - - if p.Err != nil { - pendingResponse.err = p.Err - } - - pendingResponse.response.atta = p.Body.(*Response).atta - - if pendingResponse.callback == nil { - pendingResponse.done <- struct{}{} - } else { - pendingResponse.callback(pendingResponse.GetCallResponse()) - } -} - -// OnCron notified when RPC client session got any message in cron job -func (h *RpcClientHandler) OnCron(session getty.Session) { - clientRpcSession, err := h.conn.getClientRpcSession(session) - if err != nil { - logger.Errorf("client.getClientSession(session{%s}) = error{%v}", - session.Stat(), perrors.WithStack(err)) - return - } - 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(), clientRpcSession.reqNum) - h.conn.removeSession(session) // -> h.conn.close() -> h.conn.pool.remove(h.conn) - return - } - - h.conn.pool.rpcClient.heartbeat(session) -} - -// ////////////////////////////////////////// -// RpcServerHandler -// ////////////////////////////////////////// - -// RpcServerHandler is handler of RPC Server -type RpcServerHandler struct { - maxSessionNum int - sessionTimeout time.Duration - sessionMap map[getty.Session]*rpcSession - rwlock sync.RWMutex -} - -// NewRpcServerHandler creates RpcServerHandler with @maxSessionNum and @sessionTimeout -func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration) *RpcServerHandler { - return &RpcServerHandler{ - maxSessionNum: maxSessionNum, - sessionTimeout: sessionTimeout, - sessionMap: make(map[getty.Session]*rpcSession), - } -} - -// OnOpen notified when RPC server session opened -func (h *RpcServerHandler) OnOpen(session getty.Session) error { - var err error - h.rwlock.RLock() - if h.maxSessionNum <= len(h.sessionMap) { - err = errTooManySessions - } - h.rwlock.RUnlock() - if err != nil { - return perrors.WithStack(err) - } - - logger.Infof("got session:%s", session.Stat()) - h.rwlock.Lock() - h.sessionMap[session] = &rpcSession{session: session} - h.rwlock.Unlock() - return nil -} - -// OnError notified when RPC server session got any error -func (h *RpcServerHandler) OnError(session getty.Session, err error) { - logger.Warnf("session{%s} got error{%v}, will be closed.", session.Stat(), err) - h.rwlock.Lock() - delete(h.sessionMap, session) - h.rwlock.Unlock() -} - -// 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() - delete(h.sessionMap, session) - h.rwlock.Unlock() -} - -// 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 { - h.sessionMap[session].reqNum++ - } - h.rwlock.Unlock() - - p, ok := pkg.(*DubboPackage) - if !ok { - logger.Errorf("illegal package{%#v}", pkg) - return - } - p.Header.ResponseStatus = hessian.Response_OK - - // heartbeat - if p.Header.Type&hessian.PackageHeartbeat != 0x00 { - logger.Debugf("get rpc heartbeat request{header: %#v, service: %#v, body: %#v}", p.Header, p.Service, p.Body) - reply(session, p, hessian.PackageHeartbeat) - return - } - - twoway := true - // not twoway - if p.Header.Type&hessian.PackageRequest_TwoWay == 0x00 { - twoway = false - } - - defer func() { - if e := recover(); e != nil { - p.Header.ResponseStatus = hessian.Response_SERVER_ERROR - if err, ok := e.(error); ok { - logger.Errorf("OnMessage panic: %+v", perrors.WithStack(err)) - p.Body = perrors.WithStack(err) - } else if err, ok := e.(string); ok { - logger.Errorf("OnMessage panic: %+v", perrors.New(err)) - p.Body = perrors.New(err) - } else { - logger.Errorf("OnMessage panic: %+v, this is impossible.", e) - p.Body = e - } - - if !twoway { - return - } - reply(session, p, hessian.PackageResponse) - } - - }() - - u := common.NewURLWithOptions(common.WithPath(p.Service.Path), common.WithParams(url.Values{}), - common.WithParamsValue(constant.GROUP_KEY, p.Service.Group), - common.WithParamsValue(constant.INTERFACE_KEY, p.Service.Interface), - common.WithParamsValue(constant.VERSION_KEY, p.Service.Version)) - exporter, _ := dubboProtocol.ExporterMap().Load(u.ServiceKey()) - if exporter == nil { - err := fmt.Errorf("don't have this exporter, key: %s", u.ServiceKey()) - logger.Errorf(err.Error()) - p.Header.ResponseStatus = hessian.Response_OK - p.Body = err - reply(session, p, hessian.PackageResponse) - return - } - invoker := exporter.(protocol.Exporter).GetInvoker() - if invoker != nil { - attachments := p.Body.(map[string]interface{})["attachments"].(map[string]string) - attachments[constant.LOCAL_ADDR] = session.LocalAddr() - attachments[constant.REMOTE_ADDR] = session.RemoteAddr() - - args := p.Body.(map[string]interface{})["args"].([]interface{}) - inv := invocation.NewRPCInvocation(p.Service.Method, args, attachments) - - ctx := rebuildCtx(inv) - - result := invoker.Invoke(ctx, inv) - if err := result.Error(); err != nil { - p.Header.ResponseStatus = hessian.Response_OK - p.Body = hessian.NewResponse(nil, err, result.Attachments()) - } else { - res := result.Result() - p.Header.ResponseStatus = hessian.Response_OK - p.Body = hessian.NewResponse(res, nil, result.Attachments()) - } - } - - if !twoway { - return - } - reply(session, p, hessian.PackageResponse) -} - -// OnCron notified when RPC server session got any message in cron job -func (h *RpcServerHandler) OnCron(session getty.Session) { - var ( - flag bool - active time.Time - ) - - h.rwlock.RLock() - if _, ok := h.sessionMap[session]; ok { - active = session.GetActive() - if h.sessionTimeout.Nanoseconds() < time.Since(active).Nanoseconds() { - flag = true - logger.Warnf("session{%s} timeout{%s}, reqNum{%d}", - session.Stat(), time.Since(active).String(), h.sessionMap[session].reqNum) - } - } - h.rwlock.RUnlock() - - if flag { - h.rwlock.Lock() - delete(h.sessionMap, session) - h.rwlock.Unlock() - session.Close() - } -} - -// rebuildCtx rebuild the context by attachment. -// Once we decided to transfer more context's key-value, we should change this. -// now we only support rebuild the tracing context -func rebuildCtx(inv *invocation.RPCInvocation) context.Context { - ctx := context.Background() - - // actually, if user do not use any opentracing framework, the err will not be nil. - spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, - opentracing.TextMapCarrier(inv.Attachments())) - if err == nil { - ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) - } - return ctx -} - -func reply(session getty.Session, req *DubboPackage, tp hessian.PackageType) { - resp := &DubboPackage{ - Header: hessian.DubboHeader{ - SerialID: req.Header.SerialID, - Type: tp, - ID: req.Header.ID, - ResponseStatus: req.Header.ResponseStatus, - }, - } - - if req.Header.Type&hessian.PackageRequest != 0x00 { - resp.Body = req.Body - } else { - resp.Body = 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/opentracing.go b/protocol/dubbo/opentracing.go new file mode 100644 index 0000000000000000000000000000000000000000..2dcd6a4d0d9f491ba6d51ea7a3ba96812a6f9e08 --- /dev/null +++ b/protocol/dubbo/opentracing.go @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dubbo + +import ( + "github.com/opentracing/opentracing-go" +) +import ( + invocation_impl "github.com/apache/dubbo-go/protocol/invocation" +) + +func injectTraceCtx(currentSpan opentracing.Span, inv *invocation_impl.RPCInvocation) error { + // inject opentracing ctx + traceAttachments := filterContext(inv.Attachments()) + carrier := opentracing.TextMapCarrier(traceAttachments) + err := opentracing.GlobalTracer().Inject(currentSpan.Context(), opentracing.TextMap, carrier) + if err == nil { + fillTraceAttachments(inv.Attachments(), traceAttachments) + } + return err +} + +func extractTraceCtx(inv *invocation_impl.RPCInvocation) (opentracing.SpanContext, error) { + traceAttachments := filterContext(inv.Attachments()) + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(traceAttachments)) + return spanCtx, err +} + +func filterContext(attachments map[string]interface{}) map[string]string { + var traceAttchment = make(map[string]string) + for k, v := range attachments { + if r, ok := v.(string); ok { + traceAttchment[k] = r + } + } + return traceAttchment +} + +func fillTraceAttachments(attachments map[string]interface{}, traceAttachment map[string]string) { + for k, v := range traceAttachment { + attachments[k] = v + } +} diff --git a/protocol/dubbo/readwriter.go b/protocol/dubbo/readwriter.go deleted file mode 100644 index adc6311b0ee6b050a5da5ebb1b95472fe0602503..0000000000000000000000000000000000000000 --- a/protocol/dubbo/readwriter.go +++ /dev/null @@ -1,190 +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 dubbo - -import ( - "bytes" - "reflect" -) - -import ( - "github.com/apache/dubbo-getty" - "github.com/apache/dubbo-go-hessian2" - perrors "github.com/pkg/errors" -) - -import ( - "github.com/apache/dubbo-go/common" - "github.com/apache/dubbo-go/common/constant" - "github.com/apache/dubbo-go/common/logger" -) - -//////////////////////////////////////////// -// RpcClientPackageHandler -//////////////////////////////////////////// - -// RpcClientPackageHandler handle package for client in getty. -type RpcClientPackageHandler struct { - client *Client -} - -// 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{} - - buf := bytes.NewBuffer(data) - err := pkg.Unmarshal(buf, p.client) - if err != nil { - originErr := perrors.Cause(err) - if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { - return nil, 0, nil - } - - logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) - - return nil, 0, perrors.WithStack(err) - } - - if pkg.Header.Type&hessian.PackageRequest == 0x00 { - pkg.Err = pkg.Body.(*hessian.Response).Exception - pkg.Body = NewResponse(pkg.Body.(*hessian.Response).RspObj, pkg.Body.(*hessian.Response).Attachments) - } - - return pkg, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil -} - -// Write encode @pkg. -func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { - req, ok := pkg.(*DubboPackage) - if !ok { - logger.Errorf("illegal pkg:%+v\n", pkg) - return nil, perrors.New("invalid rpc request") - } - - buf, err := req.Marshal() - if err != nil { - logger.Warnf("binary.Write(req{%#v}) = err{%#v}", req, perrors.WithStack(err)) - return nil, perrors.WithStack(err) - } - - return buf.Bytes(), nil -} - -//////////////////////////////////////////// -// RpcServerPackageHandler -//////////////////////////////////////////// - -var ( - rpcServerPkgHandler = &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), - } - - buf := bytes.NewBuffer(data) - err := pkg.Unmarshal(buf) - if err != nil { - originErr := perrors.Cause(err) - if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { - return nil, 0, nil - } - - logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) - - return nil, 0, perrors.WithStack(err) - } - - if pkg.Header.Type&hessian.PackageHeartbeat == 0x00 { - // convert params of request - req := pkg.Body.([]interface{}) // length of body should be 7 - if len(req) > 0 { - var dubboVersion, argsTypes string - var args []interface{} - var attachments map[string]string - if req[0] != nil { - dubboVersion = req[0].(string) - } - if req[1] != nil { - pkg.Service.Path = req[1].(string) - } - if req[2] != nil { - pkg.Service.Version = req[2].(string) - } - if req[3] != nil { - pkg.Service.Method = req[3].(string) - } - if req[4] != nil { - argsTypes = req[4].(string) - } - if req[5] != nil { - args = req[5].([]interface{}) - } - if req[6] != nil { - attachments = req[6].(map[string]string) - } - if pkg.Service.Path == "" && len(attachments[constant.PATH_KEY]) > 0 { - pkg.Service.Path = attachments[constant.PATH_KEY] - } - if _, ok := attachments[constant.INTERFACE_KEY]; ok { - pkg.Service.Interface = attachments[constant.INTERFACE_KEY] - } else { - pkg.Service.Interface = pkg.Service.Path - } - if len(attachments[constant.GROUP_KEY]) > 0 { - pkg.Service.Group = attachments[constant.GROUP_KEY] - } - pkg.Body = map[string]interface{}{ - "dubboVersion": dubboVersion, - "argsTypes": argsTypes, - "args": args, - "service": common.ServiceMap.GetService(DUBBO, pkg.Service.Path), // path as a key - "attachments": attachments, - } - } - } - - 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 { - logger.Errorf("illegal pkg:%+v\n, it is %+v", pkg, reflect.TypeOf(pkg)) - return nil, perrors.New("invalid rpc response") - } - - buf, err := res.Marshal() - if err != nil { - logger.Warnf("binary.Write(res{%#v}) = err{%#v}", res, perrors.WithStack(err)) - return nil, perrors.WithStack(err) - } - - return buf.Bytes(), nil -} diff --git a/protocol/grpc/client.go b/protocol/grpc/client.go index a0ab0be80cc905115e675c1c4dea2b1c748f6c09..33f0a088dcd6800ce7376faa3d5e05ab5d62596d 100644 --- a/protocol/grpc/client.go +++ b/protocol/grpc/client.go @@ -19,6 +19,7 @@ package grpc import ( "reflect" + "strconv" ) import ( @@ -89,13 +90,17 @@ type Client struct { } // NewClient creates a new gRPC client. -func NewClient(url common.URL) *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() dailOpts := make([]grpc.DialOption, 0, 4) + maxMessageSize, _ := strconv.Atoi(url.GetParam(constant.MESSAGE_SIZE_KEY, "4")) dailOpts = append(dailOpts, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithUnaryInterceptor( otgrpc.OpenTracingClientInterceptor(tracer, otgrpc.LogPayloads())), - grpc.WithDefaultCallOptions(grpc.CallContentSubtype(clientConf.ContentSubType))) + grpc.WithDefaultCallOptions( + grpc.CallContentSubtype(clientConf.ContentSubType), + grpc.MaxCallRecvMsgSize(1024*1024*maxMessageSize), + grpc.MaxCallSendMsgSize(1024*1024*maxMessageSize))) conn, err := grpc.Dial(url.Location, dailOpts...) if err != nil { panic(err) @@ -112,7 +117,7 @@ func NewClient(url common.URL) *Client { } func getInvoker(impl interface{}, conn *grpc.ClientConn) interface{} { - in := []reflect.Value{} + var in []reflect.Value in = append(in, reflect.ValueOf(conn)) method := reflect.ValueOf(impl).MethodByName("GetDubboStub") res := method.Call(in) diff --git a/protocol/grpc/grpc_exporter.go b/protocol/grpc/grpc_exporter.go index 0dc764854d61576892800180041c53f0a7735c7c..5beb4fedb1aa0285288d5b2b52515d4f6b9498e8 100644 --- a/protocol/grpc/grpc_exporter.go +++ b/protocol/grpc/grpc_exporter.go @@ -42,10 +42,9 @@ func NewGrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map // 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(interfaceName, GRPC, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, GRPC, gg.GetInvoker().GetUrl().ServiceKey()) 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 737e8c40a063b07e56d7c90f7de04670461ce103..02e7716115e6bb22f3de5a8c4d8a2131995e81c6 100644 --- a/protocol/grpc/grpc_invoker.go +++ b/protocol/grpc/grpc_invoker.go @@ -45,7 +45,7 @@ type GrpcInvoker struct { } // NewGrpcInvoker returns a Grpc invoker instance -func NewGrpcInvoker(url common.URL, client *Client) *GrpcInvoker { +func NewGrpcInvoker(url *common.URL, client *Client) *GrpcInvoker { return &GrpcInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: client, @@ -62,7 +62,7 @@ func (gi *GrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocatio result.Err = errNoReply } - in := []reflect.Value{} + var in []reflect.Value in = append(in, reflect.ValueOf(context.Background())) in = append(in, invocation.ParameterValues()...) diff --git a/protocol/grpc/grpc_protocol.go b/protocol/grpc/grpc_protocol.go index 68594a4b35921b6e3b1d59d404ed163025d57a81..3ad124532f389d6df5ef746eaca26158b8c42790 100644 --- a/protocol/grpc/grpc_protocol.go +++ b/protocol/grpc/grpc_protocol.go @@ -18,11 +18,13 @@ package grpc import ( + "strconv" "sync" ) 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/protocol" @@ -65,7 +67,7 @@ func (gp *GrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { return exporter } -func (gp *GrpcProtocol) openServer(url common.URL) { +func (gp *GrpcProtocol) openServer(url *common.URL) { _, ok := gp.serverMap[url.Location] if !ok { _, ok := gp.ExporterMap().Load(url.ServiceKey()) @@ -76,7 +78,9 @@ func (gp *GrpcProtocol) openServer(url common.URL) { gp.serverLock.Lock() _, ok = gp.serverMap[url.Location] if !ok { + grpcMessageSize, _ := strconv.Atoi(url.GetParam(constant.MESSAGE_SIZE_KEY, "4")) srv := NewServer() + srv.SetBufferSize(grpcMessageSize) gp.serverMap[url.Location] = srv srv.Start(url) } @@ -85,7 +89,7 @@ func (gp *GrpcProtocol) openServer(url common.URL) { } // Refer a remote gRPC service -func (gp *GrpcProtocol) Refer(url common.URL) protocol.Invoker { +func (gp *GrpcProtocol) Refer(url *common.URL) protocol.Invoker { invoker := NewGrpcInvoker(url, NewClient(url)) gp.SetInvokers(invoker) logger.Infof("Refer service: %s", url.String()) diff --git a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go index 1af4fafdc606783e937ede63f99e5a08f0b2419e..a9f50e82879e1d6448300ec7b2a92e2a68070dc8 100644 --- a/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go +++ b/protocol/grpc/protoc-gen-dubbo/plugin/dubbo/dubbo.go @@ -220,7 +220,23 @@ func (g *dubboGrpc) generateService(file *generator.FileDescriptor, service *pb. g.P("},") } g.P("},") - g.P("Streams: []", grpcPkg, ".StreamDesc{},") + g.P("Streams: []", grpcPkg, ".StreamDesc{") + for i, method := range service.Method { + if !method.GetClientStreaming() && !method.GetServerStreaming() { + continue + } + g.P("{") + g.P("StreamName: ", strconv.Quote(method.GetName()), ",") + g.P("Handler: ", handlerNames[i], ",") + if method.GetServerStreaming() { + g.P("ServerStreams: true,") + } + if method.GetClientStreaming() { + g.P("ClientStreams: true,") + } + g.P("},") + } + g.P("},") g.P("Metadata: \"", file.GetName(), "\",") g.P("}") g.P("}") @@ -241,6 +257,7 @@ func (g *dubboGrpc) generateClientSignature(servName string, method *pb.MethodDe respName := "out *" + g.typeName(method.GetOutputType()) if method.GetServerStreaming() || method.GetClientStreaming() { respName = servName + "_" + generator.CamelCase(origMethName) + "Client" + return fmt.Sprintf("%s func(ctx %s.Context%s) (%s, error)", methName, contextPkg, reqArg, respName) } return fmt.Sprintf("%s func(ctx %s.Context%s, %s) error", methName, contextPkg, reqArg, respName) } @@ -252,7 +269,6 @@ func (g *dubboGrpc) generateServerMethod(servName, fullServName string, method * methName := generator.CamelCase(method.GetName()) hname := fmt.Sprintf("_DUBBO_%s_%s_Handler", servName, methName) inType := g.typeName(method.GetInputType()) - outType := g.typeName(method.GetOutputType()) if !method.GetServerStreaming() && !method.GetClientStreaming() { g.P("func ", hname, "(srv interface{}, ctx ", contextPkg, ".Context, dec func(interface{}) error, interceptor ", grpcPkg, ".UnaryServerInterceptor) (interface{}, error) {") @@ -286,6 +302,11 @@ func (g *dubboGrpc) generateServerMethod(servName, fullServName string, method * } streamType := unexport(servName) + methName + "Server" g.P("func ", hname, "(srv interface{}, stream ", grpcPkg, ".ServerStream) error {") + g.P("_, ok := srv.(dgrpc.DubboGrpcService)") + g.P(`invo := invocation.NewRPCInvocation("`, methName, `", nil, nil)`) + g.P("if !ok {") + g.P("fmt.Println(invo)") + g.P("}") if !method.GetClientStreaming() { g.P("m := new(", inType, ")") g.P("if err := stream.RecvMsg(m); err != nil { return err }") @@ -296,50 +317,5 @@ func (g *dubboGrpc) generateServerMethod(servName, fullServName string, method * g.P("}") g.P() - genSend := method.GetServerStreaming() - genSendAndClose := !method.GetServerStreaming() - genRecv := method.GetClientStreaming() - - // Stream auxiliary types and methods. - g.P("type ", servName, "_", methName, "Server interface {") - if genSend { - g.P("Send(*", outType, ") error") - } - if genSendAndClose { - g.P("SendAndClose(*", outType, ") error") - } - if genRecv { - g.P("Recv() (*", inType, ", error)") - } - g.P(grpcPkg, ".ServerStream") - g.P("}") - g.P() - - g.P("type ", streamType, " struct {") - g.P(grpcPkg, ".ServerStream") - g.P("}") - g.P() - - if genSend { - g.P("func (x *", streamType, ") Send(m *", outType, ") error {") - g.P("return x.ServerStream.SendMsg(m)") - g.P("}") - g.P() - } - if genSendAndClose { - g.P("func (x *", streamType, ") SendAndClose(m *", outType, ") error {") - g.P("return x.ServerStream.SendMsg(m)") - g.P("}") - g.P() - } - if genRecv { - g.P("func (x *", streamType, ") Recv() (*", inType, ", error) {") - g.P("m := new(", inType, ")") - g.P("if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err }") - g.P("return m, nil") - g.P("}") - g.P() - } - return hname } diff --git a/protocol/grpc/server.go b/protocol/grpc/server.go index 2b7b1adddf573e3b84db32a11cfc286ff22a0276..e77e2babd0aba81cdcfe2288eeff69195decf9f4 100644 --- a/protocol/grpc/server.go +++ b/protocol/grpc/server.go @@ -40,6 +40,7 @@ import ( // Server is a gRPC server type Server struct { grpcServer *grpc.Server + bufferSize int } // NewServer creates a new server @@ -57,8 +58,12 @@ type DubboGrpcService interface { ServiceDesc() *grpc.ServiceDesc } +func (s *Server) SetBufferSize(n int) { + s.bufferSize = n +} + // Start gRPC server with @url -func (s *Server) Start(url common.URL) { +func (s *Server) Start(url *common.URL) { var ( addr string err error @@ -72,7 +77,9 @@ func (s *Server) Start(url common.URL) { // if global trace instance was set, then server tracer instance can be get. If not , will return Nooptracer tracer := opentracing.GlobalTracer() server := grpc.NewServer( - grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer))) + grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer)), + grpc.MaxRecvMsgSize(1024*1024*s.bufferSize), + grpc.MaxSendMsgSize(1024*1024*s.bufferSize)) key := url.GetParam(constant.BEAN_NAME_KEY, "") service := config.GetProviderService(key) diff --git a/protocol/invocation.go b/protocol/invocation.go index 296ec0540c1eed69c30e1b1477be038a4a9cc00e..2ecc817cc91e08fb6be10a0348477943d05bb1e3 100644 --- a/protocol/invocation.go +++ b/protocol/invocation.go @@ -25,6 +25,8 @@ import ( type Invocation interface { // MethodName gets invocation method name. MethodName() string + // ParameterTypeNames gets invocation parameter type names. + ParameterTypeNames() []string // ParameterTypes gets invocation parameter types. ParameterTypes() []reflect.Type // ParameterValues gets invocation parameter values. @@ -34,15 +36,16 @@ type Invocation 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 + Attachments() map[string]interface{} + // AttachmentsByKey gets attachment by key , if nil then return default value. 锛圛t will be deprecated in the future锛� AttachmentsByKey(string, string) string + Attachment(string) interface{} // 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{} // SetAttachments sets attribute by @key and @value. - SetAttachments(key string, value string) + SetAttachments(key string, value interface{}) // Invoker gets the invoker in current context. Invoker() Invoker } diff --git a/protocol/invocation/rpcinvocation.go b/protocol/invocation/rpcinvocation.go index c72e105d3594195457a7d7abcccc8badb85c678b..ec68ca38991df1c1eae88f3ea64cca34c6981804 100644 --- a/protocol/invocation/rpcinvocation.go +++ b/protocol/invocation/rpcinvocation.go @@ -23,6 +23,8 @@ import ( ) import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol" ) @@ -33,13 +35,16 @@ import ( // todo: is it necessary to separate fields of consumer(provider) from RPCInvocation // nolint type RPCInvocation struct { - methodName string - parameterTypes []reflect.Type + methodName string + // Parameter Type Names. It is used to specify the parameterType + parameterTypeNames []string + parameterTypes []reflect.Type + parameterValues []reflect.Value arguments []interface{} reply interface{} callBack interface{} - attachments map[string]string + attachments map[string]interface{} // Refer to dubbo 2.7.6. It is different from attachment. It is used in internal process. attributes map[string]interface{} invoker protocol.Invoker @@ -47,7 +52,7 @@ type RPCInvocation struct { } // NewRPCInvocation creates a RPC invocation. -func NewRPCInvocation(methodName string, arguments []interface{}, attachments map[string]string) *RPCInvocation { +func NewRPCInvocation(methodName string, arguments []interface{}, attachments map[string]interface{}) *RPCInvocation { return &RPCInvocation{ methodName: methodName, arguments: arguments, @@ -78,6 +83,11 @@ func (r *RPCInvocation) ParameterTypes() []reflect.Type { return r.parameterTypes } +// ParameterTypeNames gets RPC invocation parameter types of string expression. +func (r *RPCInvocation) ParameterTypeNames() []string { + return r.parameterTypeNames +} + // ParameterValues gets RPC invocation parameter values. func (r *RPCInvocation) ParameterValues() []reflect.Value { return r.parameterValues @@ -99,7 +109,7 @@ func (r *RPCInvocation) SetReply(reply interface{}) { } // Attachments gets all attachments of RPC. -func (r *RPCInvocation) Attachments() map[string]string { +func (r *RPCInvocation) Attachments() map[string]interface{} { return r.attachments } @@ -112,11 +122,25 @@ func (r *RPCInvocation) AttachmentsByKey(key string, defaultValue string) string } value, ok := r.attachments[key] if ok { - return value + return value.(string) } return defaultValue } +// Attachment returns the corresponding value from dubbo's attachment with the given key. +func (r *RPCInvocation) Attachment(key string) interface{} { + r.lock.RLock() + defer r.lock.RUnlock() + if r.attachments == nil { + return nil + } + value, ok := r.attachments[key] + if ok { + return value + } + return nil +} + // Attributes gets all attributes of RPC. func (r *RPCInvocation) Attributes() map[string]interface{} { return r.attributes @@ -134,11 +158,11 @@ func (r *RPCInvocation) AttributeByKey(key string, defaultValue interface{}) int } // SetAttachments sets attribute by @key and @value. -func (r *RPCInvocation) SetAttachments(key string, value string) { +func (r *RPCInvocation) SetAttachments(key string, value interface{}) { r.lock.Lock() defer r.lock.Unlock() if r.attachments == nil { - r.attachments = make(map[string]string) + r.attachments = make(map[string]interface{}) } r.attachments[key] = value } @@ -172,6 +196,11 @@ func (r *RPCInvocation) SetCallBack(c interface{}) { r.callBack = c } +func (r *RPCInvocation) ServiceKey() string { + return common.ServiceKey(r.AttachmentsByKey(constant.INTERFACE_KEY, ""), + r.AttachmentsByKey(constant.GROUP_KEY, ""), r.AttachmentsByKey(constant.VERSION_KEY, "")) +} + // ///////////////////////// // option // ///////////////////////// @@ -192,6 +221,18 @@ func WithParameterTypes(parameterTypes []reflect.Type) option { } } +// WithParameterTypeNames creates option with @parameterTypeNames. +func WithParameterTypeNames(parameterTypeNames []string) option { + return func(invo *RPCInvocation) { + if len(parameterTypeNames) == 0 { + return + } + parameterTypeNamesTmp := make([]string, len(parameterTypeNames)) + copy(parameterTypeNamesTmp, parameterTypeNames) + invo.parameterTypeNames = parameterTypeNamesTmp + } +} + // WithParameterValues creates option with @parameterValues func WithParameterValues(parameterValues []reflect.Value) option { return func(invo *RPCInvocation) { @@ -221,7 +262,7 @@ func WithCallBack(callBack interface{}) option { } // WithAttachments creates option with @attachments. -func WithAttachments(attachments map[string]string) option { +func WithAttachments(attachments map[string]interface{}) option { return func(invo *RPCInvocation) { invo.attachments = attachments } diff --git a/protocol/invoker.go b/protocol/invoker.go index 3ca370479cbe2255f26628b855b11b07396f1b6d..5657b6b5b2c6d13e001bec2ba3ca7b4f02fc7e60 100644 --- a/protocol/invoker.go +++ b/protocol/invoker.go @@ -26,7 +26,7 @@ import ( "github.com/apache/dubbo-go/common/logger" ) -// Invoker ... +// Invoker the service invocation interface for the consumer //go:generate mockgen -source invoker.go -destination mock/mock_invoker.go -self_package github.com/apache/dubbo-go/protocol/mock --package mock Invoker // Extension - Invoker type Invoker interface { @@ -41,13 +41,13 @@ type Invoker interface { // BaseInvoker provides default invoker implement type BaseInvoker struct { - url common.URL + url *common.URL available bool destroyed bool } // NewBaseInvoker creates a new BaseInvoker -func NewBaseInvoker(url common.URL) *BaseInvoker { +func NewBaseInvoker(url *common.URL) *BaseInvoker { return &BaseInvoker{ url: url, available: true, @@ -56,7 +56,7 @@ func NewBaseInvoker(url common.URL) *BaseInvoker { } // GetUrl gets base invoker URL -func (bi *BaseInvoker) GetUrl() common.URL { +func (bi *BaseInvoker) GetUrl() *common.URL { return bi.url } diff --git a/protocol/jsonrpc/http.go b/protocol/jsonrpc/http.go index 2a2ddfeeeb52b865e96ccff69d2b39d8a671ed41..869617ea4eb512df6287d21e9d811145c1944e57 100644 --- a/protocol/jsonrpc/http.go +++ b/protocol/jsonrpc/http.go @@ -101,7 +101,7 @@ func NewHTTPClient(opt *HTTPOptions) *HTTPClient { } // NewRequest creates a new HTTP request with @service ,@method and @arguments. -func (c *HTTPClient) NewRequest(service common.URL, method string, args interface{}) *Request { +func (c *HTTPClient) NewRequest(service *common.URL, method string, args interface{}) *Request { return &Request{ ID: atomic.AddInt64(&c.ID, 1), @@ -115,7 +115,7 @@ func (c *HTTPClient) NewRequest(service common.URL, method string, args interfac } // 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 { +func (c *HTTPClient) Call(ctx context.Context, service *common.URL, req *Request, rsp interface{}) error { // header httpHeader := http.Header{} httpHeader.Set("Content-Type", "application/json") diff --git a/protocol/jsonrpc/http_test.go b/protocol/jsonrpc/http_test.go index 576591940dd3021e7bbd9d2eda0ac5498391a1f7..c4801c8db883353c82762162a7b658f964fc6ffa 100644 --- a/protocol/jsonrpc/http_test.go +++ b/protocol/jsonrpc/http_test.go @@ -49,7 +49,7 @@ type ( ) const ( - mockJsonCommonUrl = "jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" + + mockJsonCommonUrl = "jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&" + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + @@ -58,7 +58,7 @@ const ( func TestHTTPClientCall(t *testing.T) { - methods, err := common.ServiceMap.Register("com.ikurento.user.UserProvider", "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) @@ -75,7 +75,7 @@ func TestHTTPClientCall(t *testing.T) { // call GetUser ctx := context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser", }) @@ -89,7 +89,7 @@ func TestHTTPClientCall(t *testing.T) { // call GetUser0 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser0", }) @@ -102,7 +102,7 @@ func TestHTTPClientCall(t *testing.T) { // call GetUser1 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser1", }) @@ -114,7 +114,7 @@ func TestHTTPClientCall(t *testing.T) { // call GetUser2 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser2", }) @@ -126,7 +126,7 @@ func TestHTTPClientCall(t *testing.T) { // call GetUser3 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser3", }) @@ -138,7 +138,7 @@ func TestHTTPClientCall(t *testing.T) { // call GetUser4 ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser4", }) @@ -149,7 +149,7 @@ func TestHTTPClientCall(t *testing.T) { assert.Equal(t, &User{Id: "", Name: ""}, reply) ctx = context.WithValue(context.Background(), constant.DUBBOGO_CTX_KEY, map[string]string{ - "X-Proxy-Id": "dubbogo", + "X-Proxy-ID": "dubbogo", "X-Services": url.Path, "X-Method": "GetUser4", }) diff --git a/protocol/jsonrpc/jsonrpc_exporter.go b/protocol/jsonrpc/jsonrpc_exporter.go index 6f75f6aeae9fb1a8d75ded5f558e0ddae84686a0..6b91d266a704d928bf1a308a79044055c9a64328 100644 --- a/protocol/jsonrpc/jsonrpc_exporter.go +++ b/protocol/jsonrpc/jsonrpc_exporter.go @@ -42,10 +42,9 @@ func NewJsonrpcExporter(key string, invoker protocol.Invoker, exporterMap *sync. // 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(interfaceName, JSONRPC, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, JSONRPC, je.GetInvoker().GetUrl().ServiceKey()) 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 d84b980216ade6e569e68af31fc90e1ea16b3056..357443f5d4efbe5159fb6c0e09d3dab51266dc73 100644 --- a/protocol/jsonrpc/jsonrpc_invoker.go +++ b/protocol/jsonrpc/jsonrpc_invoker.go @@ -36,7 +36,7 @@ type JsonrpcInvoker struct { } // NewJsonrpcInvoker creates JSON RPC invoker with @url and @client -func NewJsonrpcInvoker(url common.URL, client *HTTPClient) *JsonrpcInvoker { +func NewJsonrpcInvoker(url *common.URL, client *HTTPClient) *JsonrpcInvoker { return &JsonrpcInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: client, @@ -45,10 +45,7 @@ func NewJsonrpcInvoker(url common.URL, client *HTTPClient) *JsonrpcInvoker { // Invoke the JSON RPC invocation and return result. func (ji *JsonrpcInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { - - var ( - result protocol.RPCResult - ) + var result protocol.RPCResult inv := invocation.(*invocation_impl.RPCInvocation) url := ji.GetUrl() diff --git a/protocol/jsonrpc/jsonrpc_invoker_test.go b/protocol/jsonrpc/jsonrpc_invoker_test.go index d7124ca07c6ba6d79dc72e7fb6bd98cd4b3a97b2..12a57052eba6e3a2af1b7cfb15e6ab368a7da286 100644 --- a/protocol/jsonrpc/jsonrpc_invoker_test.go +++ b/protocol/jsonrpc/jsonrpc_invoker_test.go @@ -36,13 +36,13 @@ import ( func TestJsonrpcInvokerInvoke(t *testing.T) { - methods, err := common.ServiceMap.Register("UserProvider", "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) // Export proto := GetProtocol() - url, err := common.NewURL("jsonrpc://127.0.0.1:20001/UserProvider?anyhost=true&" + + url, err := common.NewURL("jsonrpc://127.0.0.1:20001/com.ikurento.user.UserProvider?anyhost=true&" + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + diff --git a/protocol/jsonrpc/jsonrpc_protocol.go b/protocol/jsonrpc/jsonrpc_protocol.go index 90a6bf5ef7aa017488f723804b22cc613850bcf2..643bcde8cb41a3cb94e97b2d21df26894c574b28 100644 --- a/protocol/jsonrpc/jsonrpc_protocol.go +++ b/protocol/jsonrpc/jsonrpc_protocol.go @@ -59,7 +59,7 @@ func NewJsonrpcProtocol() *JsonrpcProtocol { } } -// Export JSON RPC service for remote invocation +// Export JSON RPC service for remote invocation func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { url := invoker.GetUrl() serviceKey := strings.TrimPrefix(url.Path, "/") @@ -75,7 +75,7 @@ func (jp *JsonrpcProtocol) Export(invoker protocol.Invoker) protocol.Exporter { } // Refer a remote JSON PRC service from registry -func (jp *JsonrpcProtocol) Refer(url common.URL) protocol.Invoker { +func (jp *JsonrpcProtocol) Refer(url *common.URL) protocol.Invoker { //default requestTimeout var requestTimeout = config.GetConsumerConfig().RequestTimeout @@ -106,7 +106,7 @@ func (jp *JsonrpcProtocol) Destroy() { } } -func (jp *JsonrpcProtocol) openServer(url common.URL) { +func (jp *JsonrpcProtocol) openServer(url *common.URL) { _, ok := jp.serverMap[url.Location] if !ok { _, loadOk := jp.ExporterMap().Load(strings.TrimPrefix(url.Path, "/")) diff --git a/protocol/jsonrpc/server.go b/protocol/jsonrpc/server.go index aa458a1614df29997b05ac4462200f9e9ffffc25..755aa7da79384842d5a3f3c4364fc991d84b47df 100644 --- a/protocol/jsonrpc/server.go +++ b/protocol/jsonrpc/server.go @@ -127,10 +127,10 @@ func (s *Server) handlePkg(conn net.Conn) { } reqBody, err := ioutil.ReadAll(r.Body) + r.Body.Close() if err != nil { return } - r.Body.Close() reqHeader := make(map[string]string) for k := range r.Header { @@ -229,7 +229,7 @@ func accept(listener net.Listener, fn func(net.Conn)) error { } // Start JSON RPC server then ready for accept request. -func (s *Server) Start(url common.URL) { +func (s *Server) Start(url *common.URL) { listener, err := net.Listen("tcp", url.Location) if err != nil { logger.Errorf("jsonrpc server [%s] start failed: %v", url.Path, err) @@ -263,8 +263,7 @@ func (s *Server) Stop() { }) } -func serveRequest(ctx context.Context, - header map[string]string, body []byte, conn net.Conn) error { +func serveRequest(ctx context.Context, header map[string]string, body []byte, conn net.Conn) error { sendErrorResp := func(header map[string]string, body []byte) error { rsp := &http.Response{ Header: make(http.Header), @@ -324,13 +323,12 @@ func serveRequest(ctx context.Context, if err == io.EOF || err == io.ErrUnexpectedEOF { return perrors.WithStack(err) } - return perrors.New("server cannot decode request: " + err.Error()) } + path := header["Path"] methodName := codec.req.Method if len(path) == 0 || len(methodName) == 0 { - codec.ReadBody(nil) return perrors.New("service/method request ill-formed: " + path + "/" + methodName) } @@ -345,7 +343,7 @@ func serveRequest(ctx context.Context, exporter, _ := jsonrpcProtocol.ExporterMap().Load(path) invoker := exporter.(*JsonrpcExporter).GetInvoker() if invoker != nil { - result := invoker.Invoke(ctx, invocation.NewRPCInvocation(methodName, args, map[string]string{ + result := invoker.Invoke(ctx, invocation.NewRPCInvocation(methodName, args, map[string]interface{}{ constant.PATH_KEY: path, constant.VERSION_KEY: codec.req.Version})) if err := result.Error(); err != nil { diff --git a/protocol/mock/mock_invoker.go b/protocol/mock/mock_invoker.go index 0c88b47e36122dc1a9bc9345d7e93f62cd76f13b..8a0973bd128a74677e8549793b6792e67b26b443 100644 --- a/protocol/mock/mock_invoker.go +++ b/protocol/mock/mock_invoker.go @@ -59,9 +59,9 @@ func (m *MockInvoker) EXPECT() *MockInvokerMockRecorder { } // GetUrl mocks base method -func (m *MockInvoker) GetUrl() common.URL { +func (m *MockInvoker) GetUrl() *common.URL { ret := m.ctrl.Call(m, "GetUrl") - ret0, _ := ret[0].(common.URL) + ret0, _ := ret[0].(*common.URL) return ret0 } diff --git a/protocol/protocol.go b/protocol/protocol.go index 6bed5ec4c2be82edda7a642eb342381c53387460..d03e70f1ea4acbd1277d4b968be9109fdde187a9 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -32,7 +32,7 @@ type Protocol interface { // Export service for remote invocation Export(invoker Invoker) Exporter // Refer a remote service - Refer(url common.URL) Invoker + Refer(url *common.URL) Invoker // Destroy will destroy all invoker and exporter, so it only is called once. Destroy() } @@ -89,7 +89,7 @@ func (bp *BaseProtocol) Export(invoker Invoker) Exporter { } // Refer is default refer implement. -func (bp *BaseProtocol) Refer(url common.URL) Invoker { +func (bp *BaseProtocol) Refer(url *common.URL) Invoker { return NewBaseInvoker(url) } diff --git a/protocol/protocolwrapper/mock_protocol_filter.go b/protocol/protocolwrapper/mock_protocol_filter.go index 2e9ffed06f03d196c91f6679df21538612bac405..18a4e1562107939e9c72090778422e0d8bb56a60 100644 --- a/protocol/protocolwrapper/mock_protocol_filter.go +++ b/protocol/protocolwrapper/mock_protocol_filter.go @@ -39,7 +39,7 @@ func (pfw *mockProtocolFilter) Export(invoker protocol.Invoker) protocol.Exporte } // Refer a mock remote service -func (pfw *mockProtocolFilter) Refer(url common.URL) protocol.Invoker { +func (pfw *mockProtocolFilter) Refer(url *common.URL) protocol.Invoker { return protocol.NewBaseInvoker(url) } diff --git a/protocol/protocolwrapper/protocol_filter_wrapper.go b/protocol/protocolwrapper/protocol_filter_wrapper.go index 87f90d3b7c73328a30e883da8251ac8364a63b2d..79d2cf7f55ee81db39ea28448fc66a8a7494d8d1 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper.go @@ -55,7 +55,7 @@ func (pfw *ProtocolFilterWrapper) Export(invoker protocol.Invoker) protocol.Expo } // Refer a remote service -func (pfw *ProtocolFilterWrapper) Refer(url common.URL) protocol.Invoker { +func (pfw *ProtocolFilterWrapper) Refer(url *common.URL) protocol.Invoker { if pfw.protocol == nil { pfw.protocol = extension.GetProtocol(url.Protocol) } @@ -68,21 +68,19 @@ func (pfw *ProtocolFilterWrapper) Destroy() { } func buildInvokerChain(invoker protocol.Invoker, key string) protocol.Invoker { - filtName := invoker.GetUrl().GetParam(key, "") - if filtName == "" { + filterName := invoker.GetUrl().GetParam(key, "") + if filterName == "" { return invoker } - filtNames := strings.Split(filtName, ",") - next := invoker + filterNames := strings.Split(filterName, ",") // The order of filters is from left to right, so loading from right to left - - for i := len(filtNames) - 1; i >= 0; i-- { - flt := extension.GetFilter(filtNames[i]) + next := invoker + for i := len(filterNames) - 1; i >= 0; i-- { + flt := extension.GetFilter(filterNames[i]) fi := &FilterInvoker{next: next, invoker: invoker, filter: flt} next = fi } - return next } @@ -103,7 +101,7 @@ type FilterInvoker struct { } // GetUrl is used to get url from FilterInvoker -func (fi *FilterInvoker) GetUrl() common.URL { +func (fi *FilterInvoker) GetUrl() *common.URL { return fi.invoker.GetUrl() } diff --git a/protocol/protocolwrapper/protocol_filter_wrapper_test.go b/protocol/protocolwrapper/protocol_filter_wrapper_test.go index b03ea7b9b66d926aff8851f88e1bd7434b254903..8f063f85521bbdcd3fa3891e7d09754fc9b58ac7 100644 --- a/protocol/protocolwrapper/protocol_filter_wrapper_test.go +++ b/protocol/protocolwrapper/protocol_filter_wrapper_test.go @@ -43,7 +43,7 @@ func TestProtocolFilterWrapperExport(t *testing.T) { u := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.SERVICE_FILTER_KEY, "echo")) - exporter := filtProto.Export(protocol.NewBaseInvoker(*u)) + exporter := filtProto.Export(protocol.NewBaseInvoker(u)) _, ok := exporter.GetInvoker().(*FilterInvoker) assert.True(t, ok) } @@ -55,7 +55,7 @@ func TestProtocolFilterWrapperRefer(t *testing.T) { u := common.NewURLWithOptions( common.WithParams(url.Values{}), common.WithParamsValue(constant.REFERENCE_FILTER_KEY, "echo")) - invoker := filtProto.Refer(*u) + invoker := filtProto.Refer(u) _, ok := invoker.(*FilterInvoker) assert.True(t, ok) } diff --git a/protocol/rest/rest_exporter.go b/protocol/rest/rest_exporter.go index e39558caeae9811817cc26a1717c1b8e3729234c..7a49a2063559c2f4e9b4975bc86ea708abbfb026 100644 --- a/protocol/rest/rest_exporter.go +++ b/protocol/rest/rest_exporter.go @@ -42,12 +42,10 @@ func NewRestExporter(key string, invoker protocol.Invoker, exporterMap *sync.Map // Unexport unexport the RestExporter func (re *RestExporter) Unexport() { - serviceId := re.GetInvoker().GetUrl().GetParam(constant.BEAN_NAME_KEY, "") interfaceName := re.GetInvoker().GetUrl().GetParam(constant.INTERFACE_KEY, "") re.BaseExporter.Unexport() - err := common.ServiceMap.UnRegister(interfaceName, REST, serviceId) + err := common.ServiceMap.UnRegister(interfaceName, REST, re.GetInvoker().GetUrl().ServiceKey()) if err != nil { logger.Errorf("[RestExporter.Unexport] error: %v", err) } - return } diff --git a/protocol/rest/rest_invoker.go b/protocol/rest/rest_invoker.go index 691beeda4085f316075fe55b9328bc86d6328187..898890e41732d3c874c47cf3b88bba2e201d01d0 100644 --- a/protocol/rest/rest_invoker.go +++ b/protocol/rest/rest_invoker.go @@ -43,7 +43,7 @@ type RestInvoker struct { } // NewRestInvoker returns a RestInvoker -func NewRestInvoker(url common.URL, client *client.RestClient, restMethodConfig map[string]*config.RestMethodConfig) *RestInvoker { +func NewRestInvoker(url *common.URL, client *client.RestClient, restMethodConfig map[string]*config.RestMethodConfig) *RestInvoker { return &RestInvoker{ BaseInvoker: *protocol.NewBaseInvoker(url), client: *client, diff --git a/protocol/rest/rest_invoker_test.go b/protocol/rest/rest_invoker_test.go index 9df97a211e9d90daa3206b94926ceeac42df4606..18843d2ae4e8dc54683226f2fb0c325e58c7e4ed 100644 --- a/protocol/rest/rest_invoker_test.go +++ b/protocol/rest/rest_invoker_test.go @@ -65,7 +65,7 @@ func TestRestInvokerInvoke(t *testing.T) { url, err := common.NewURL(mockRestCommonUrl) assert.NoError(t, err) - _, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{}) + _, err = common.ServiceMap.Register(url.Service(), url.Protocol, "", "", &UserProvider{}) assert.NoError(t, err) con := config.ProviderConfig{} config.SetProviderConfig(con) @@ -210,6 +210,6 @@ func TestRestInvokerInvoke(t *testing.T) { assert.Error(t, res.Error(), "test error") assert.Equal(t, filterNum, 12) - err = common.ServiceMap.UnRegister("UserProvider", url.Protocol, "com.ikurento.user.UserProvider") + err = common.ServiceMap.UnRegister(url.Service(), url.Protocol, url.ServiceKey()) assert.NoError(t, err) } diff --git a/protocol/rest/rest_protocol.go b/protocol/rest/rest_protocol.go index 0cd26c240a7eb1b83a04ca2f57c332bbda994967..05e119b0540b5f887d53d006e41d3083d1c46de4 100644 --- a/protocol/rest/rest_protocol.go +++ b/protocol/rest/rest_protocol.go @@ -86,7 +86,7 @@ func (rp *RestProtocol) Export(invoker protocol.Invoker) protocol.Exporter { } // Refer create rest service reference -func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { +func (rp *RestProtocol) Refer(url *common.URL) protocol.Invoker { // create rest_invoker var requestTimeout = config.GetConsumerConfig().RequestTimeout requestTimeoutStr := url.GetParam(constant.TIMEOUT_KEY, config.GetConsumerConfig().Request_Timeout) @@ -107,7 +107,7 @@ func (rp *RestProtocol) Refer(url common.URL) protocol.Invoker { } // nolint -func (rp *RestProtocol) getServer(url common.URL, serverType string) server.RestServer { +func (rp *RestProtocol) getServer(url *common.URL, serverType string) server.RestServer { restServer, ok := rp.serverMap[url.Location] if ok { return restServer @@ -149,8 +149,8 @@ func (rp *RestProtocol) getClient(restOptions client.RestOptions, clientType str func (rp *RestProtocol) Destroy() { // destroy rest_server rp.BaseProtocol.Destroy() - for key, server := range rp.serverMap { - server.Destroy() + for key, tmpServer := range rp.serverMap { + tmpServer.Destroy() delete(rp.serverMap, key) } for key := range rp.clientMap { diff --git a/protocol/rest/rest_protocol_test.go b/protocol/rest/rest_protocol_test.go index 9ff4e7df7fe41d7fd028bf476cc2192cf7cefcca..580fc61fd6cfda731184eed6f785b80abc9fccfe 100644 --- a/protocol/rest/rest_protocol_test.go +++ b/protocol/rest/rest_protocol_test.go @@ -72,7 +72,7 @@ func TestRestProtocolExport(t *testing.T) { proto := GetRestProtocol() url, err := common.NewURL(mockRestCommonUrl) assert.NoError(t, err) - _, err = common.ServiceMap.Register("UserProvider", url.Protocol, &UserProvider{}) + _, err = common.ServiceMap.Register(url.Service(), url.Protocol, "", "", &UserProvider{}) assert.NoError(t, err) con := config.ProviderConfig{} config.SetProviderConfig(con) @@ -120,8 +120,6 @@ func TestRestProtocolExport(t *testing.T) { proto.Destroy() _, ok = proto.(*RestProtocol).serverMap[url.Location] assert.False(t, ok) - err = common.ServiceMap.UnRegister("UserProvider", url.Protocol, "com.ikurento.user.UserProvider") - assert.NoError(t, err) } type UserProvider struct { diff --git a/protocol/rest/server/rest_server.go b/protocol/rest/server/rest_server.go index fbd6fb7ad9dd81f043e4d45ee94a54e12ef89cdd..3a1cb19396b1c129aa836e228991a22426065f4a 100644 --- a/protocol/rest/server/rest_server.go +++ b/protocol/rest/server/rest_server.go @@ -23,7 +23,6 @@ import ( "net/http" "reflect" "strconv" - "strings" ) import ( @@ -43,7 +42,7 @@ const parseParameterErrorStr = "An error occurred while parsing parameters on th // RestServer user can implement this server interface type RestServer interface { // Start rest server - Start(url common.URL) + Start(url *common.URL) // Deploy a http api Deploy(restMethodConfig *rest_config.RestMethodConfig, routeFunc func(request RestServerRequest, response RestServerResponse)) // UnDeploy a http api @@ -90,7 +89,7 @@ func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethod err error args []interface{} ) - svc := common.ServiceMap.GetService(invoker.GetUrl().Protocol, strings.TrimPrefix(invoker.GetUrl().Path, "/")) + svc := common.ServiceMap.GetServiceByServiceKey(invoker.GetUrl().Protocol, invoker.GetUrl().ServiceKey()) // get method method := svc.Method()[methodConfig.MethodName] argsTypes := method.ArgsType() @@ -111,7 +110,7 @@ func GetRouteFunc(invoker protocol.Invoker, methodConfig *rest_config.RestMethod logger.Errorf("[Go Restful] WriteErrorString error:%v", err) } } - result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]string))) + result := invoker.Invoke(context.Background(), invocation.NewRPCInvocation(methodConfig.MethodName, args, make(map[string]interface{}))) if result.Error() != nil { err = resp.WriteError(http.StatusInternalServerError, result.Error()) if err != nil { diff --git a/protocol/rest/server/server_impl/go_restful_server.go b/protocol/rest/server/server_impl/go_restful_server.go index 6fb9ee8daa7383580b9144ea25954f8ead974dcc..4481f44912d8f1caf6f5250e9c13e90e99537cff 100644 --- a/protocol/rest/server/server_impl/go_restful_server.go +++ b/protocol/rest/server/server_impl/go_restful_server.go @@ -59,7 +59,7 @@ func NewGoRestfulServer() server.RestServer { // Start go-restful server // It will add all go-restful filters -func (grs *GoRestfulServer) Start(url common.URL) { +func (grs *GoRestfulServer) Start(url *common.URL) { container := restful.NewContainer() for _, filter := range filterSlice { container.Filter(filter) @@ -99,8 +99,6 @@ func (grs *GoRestfulServer) Deploy(restMethodConfig *config.RestMethodConfig, ro // 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.ws.RemoveRoute(restMethodConfig.Path, restMethodConfig.MethodType) if err != nil { logger.Warnf("[Go restful] Remove web service error:%v", err) diff --git a/protocol/result.go b/protocol/result.go index 2a33be612fd1f319c8c46cbd480865d5564b189d..a36b16d1cc56557c2976df5550f5d9c01b88619b 100644 --- a/protocol/result.go +++ b/protocol/result.go @@ -28,13 +28,14 @@ type Result interface { // Result gets invoker result. Result() interface{} // SetAttachments replaces the existing attachments with the specified param. - SetAttachments(map[string]string) + SetAttachments(map[string]interface{}) // Attachments gets all attachments - Attachments() map[string]string + Attachments() map[string]interface{} + // AddAttachment adds the specified map to existing attachments in this instance. - AddAttachment(string, string) + AddAttachment(string, interface{}) // Attachment gets attachment by key with default value. - Attachment(string, string) string + Attachment(string, interface{}) interface{} } ///////////////////////////// @@ -43,7 +44,7 @@ type Result interface { // RPCResult is default RPC result. type RPCResult struct { - Attrs map[string]string + Attrs map[string]interface{} Err error Rest interface{} } @@ -69,22 +70,22 @@ func (r *RPCResult) Result() interface{} { } // SetAttachments replaces the existing attachments with the specified param. -func (r *RPCResult) SetAttachments(attr map[string]string) { +func (r *RPCResult) SetAttachments(attr map[string]interface{}) { r.Attrs = attr } // Attachments gets all attachments -func (r *RPCResult) Attachments() map[string]string { +func (r *RPCResult) Attachments() map[string]interface{} { return r.Attrs } // AddAttachment adds the specified map to existing attachments in this instance. -func (r *RPCResult) AddAttachment(key, value string) { +func (r *RPCResult) AddAttachment(key string, value interface{}) { r.Attrs[key] = value } // Attachment gets attachment by key with default value. -func (r *RPCResult) Attachment(key, defaultValue string) string { +func (r *RPCResult) Attachment(key string, defaultValue interface{}) interface{} { v, ok := r.Attrs[key] if !ok { v = defaultValue diff --git a/protocol/rpc_status.go b/protocol/rpc_status.go index 60becfb34135470b0e69972c25a743f44efe19d5..8d443e84f06b89ed708e080285aee3b054ea02e6 100644 --- a/protocol/rpc_status.go +++ b/protocol/rpc_status.go @@ -97,25 +97,26 @@ func (rpc *RPCStatus) GetSuccessiveRequestFailureCount() int32 { } // GetURLStatus get URL RPC status. -func GetURLStatus(url common.URL) *RPCStatus { - rpcStatus, _ := serviceStatistic.LoadOrStore(url.Key(), &RPCStatus{}) +func GetURLStatus(url *common.URL) *RPCStatus { + rpcStatus, found := serviceStatistic.Load(url.Key()) + if !found { + rpcStatus, _ = serviceStatistic.LoadOrStore(url.Key(), &RPCStatus{}) + } return rpcStatus.(*RPCStatus) } // GetMethodStatus get method RPC status. -func GetMethodStatus(url common.URL, methodName string) *RPCStatus { +func GetMethodStatus(url *common.URL, methodName string) *RPCStatus { identifier := url.Key() methodMap, found := methodStatistics.Load(identifier) if !found { - methodMap = &sync.Map{} - methodStatistics.Store(identifier, methodMap) + methodMap, _ = methodStatistics.LoadOrStore(identifier, &sync.Map{}) } methodActive := methodMap.(*sync.Map) rpcStatus, found := methodActive.Load(methodName) if !found { - rpcStatus = &RPCStatus{} - methodActive.Store(methodName, rpcStatus) + rpcStatus, _ = methodActive.LoadOrStore(methodName, &RPCStatus{}) } status := rpcStatus.(*RPCStatus) @@ -123,13 +124,13 @@ func GetMethodStatus(url common.URL, methodName string) *RPCStatus { } // BeginCount gets begin count. -func BeginCount(url common.URL, methodName string) { +func BeginCount(url *common.URL, methodName string) { beginCount0(GetURLStatus(url)) beginCount0(GetMethodStatus(url, methodName)) } // EndCount gets end count. -func EndCount(url common.URL, methodName string, elapsed int64, succeeded bool) { +func EndCount(url *common.URL, methodName string, elapsed int64, succeeded bool) { endCount0(GetURLStatus(url), elapsed, succeeded) endCount0(GetMethodStatus(url, methodName), elapsed, succeeded) } diff --git a/protocol/rpc_status_test.go b/protocol/rpc_status_test.go index cc12753cf25007ab6d1010836b203aee22d78ca9..6fd449ce77fada15b32cba936691b51d3c8218ed 100644 --- a/protocol/rpc_status_test.go +++ b/protocol/rpc_status_test.go @@ -79,7 +79,7 @@ func TestGetUrlStatus(t *testing.T) { assert.Equal(t, int32(0), status.total) } -func TestbeginCount0(t *testing.T) { +func TestBeginCount0(t *testing.T) { defer CleanAllStatus() url, _ := common.NewURL(mockCommonDubboUrl) @@ -142,7 +142,7 @@ func TestAll(t *testing.T) { } -func request(url common.URL, method string, elapsed int64, active, succeeded bool) { +func request(url *common.URL, method string, elapsed int64, active, succeeded bool) { BeginCount(url, method) if !active { EndCount(url, method, elapsed, succeeded) diff --git a/registry/base_registry.go b/registry/base_registry.go index ad1a3b61741e003625612ad58409eb8615271a84..a6693be3a862eb208afc216840910c323b8e826f 100644 --- a/registry/base_registry.go +++ b/registry/base_registry.go @@ -29,7 +29,6 @@ import ( ) import ( - gxnet "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" ) @@ -53,7 +52,7 @@ var ( func init() { processID = fmt.Sprintf("%d", os.Getpid()) - localIP, _ = gxnet.GetLocalIP() + localIP = common.GetLocalIp() } type createPathFunc func(dubboPath string) error @@ -100,8 +99,8 @@ 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.RWMutex //ctl lock is a lock for services map - services map[string]common.URL // service name + protocol -> service config, for store the service registered + 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 } // InitBaseRegistry for init some local variables and set BaseRegistry's subclass to it @@ -109,14 +108,14 @@ func (r *BaseRegistry) InitBaseRegistry(url *common.URL, facadeRegistry FacadeBa r.URL = url r.birth = time.Now().UnixNano() r.done = make(chan struct{}) - r.services = make(map[string]common.URL) + r.services = make(map[string]*common.URL) r.facadeBasedRegistry = facadeRegistry return r } // GetUrl for get registry's url -func (r *BaseRegistry) GetUrl() common.URL { - return *r.URL +func (r *BaseRegistry) GetUrl() *common.URL { + return r.URL } // Destroy for graceful down @@ -133,7 +132,7 @@ func (r *BaseRegistry) Destroy() { } // Register implement interface registry to register -func (r *BaseRegistry) Register(conf common.URL) error { +func (r *BaseRegistry) Register(conf *common.URL) error { var ( ok bool err error @@ -161,11 +160,11 @@ func (r *BaseRegistry) Register(conf common.URL) error { } // UnRegister implement interface registry to unregister -func (r *BaseRegistry) UnRegister(conf common.URL) error { +func (r *BaseRegistry) UnRegister(conf *common.URL) error { var ( ok bool err error - oldURL common.URL + oldURL *common.URL ) func() { @@ -198,7 +197,7 @@ func (r *BaseRegistry) UnRegister(conf common.URL) error { } // service is for getting service path stored in url -func (r *BaseRegistry) service(c common.URL) string { +func (r *BaseRegistry) service(c *common.URL) string { return url.QueryEscape(c.Service()) } @@ -206,7 +205,7 @@ func (r *BaseRegistry) service(c common.URL) string { func (r *BaseRegistry) RestartCallBack() bool { // copy r.services - services := make([]common.URL, 0, len(r.services)) + services := make([]*common.URL, 0, len(r.services)) for _, confIf := range r.services { services = append(services, confIf) } @@ -231,16 +230,16 @@ func (r *BaseRegistry) RestartCallBack() bool { } // register for register url to registry, include init params -func (r *BaseRegistry) register(c common.URL) error { +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 { +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 { +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. ") } @@ -292,7 +291,7 @@ func (r *BaseRegistry) createPath(dubboPath string) error { } // providerRegistry for provider role do -func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values, f createPathFunc) (string, string, error) { +func (r *BaseRegistry) providerRegistry(c *common.URL, params url.Values, f createPathFunc) (string, string, error) { var ( dubboPath string rawURL string @@ -314,7 +313,7 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values, f creat // 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. - if len(c.Methods) == 0 { + if len(c.Methods) != 0 { params.Add(constant.METHODS_KEY, strings.Join(c.Methods, ",")) } logger.Debugf("provider url params:%#v", params) @@ -326,7 +325,16 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values, f creat } host += ":" + c.Port - rawURL = fmt.Sprintf("%s://%s%s?%s", c.Protocol, host, c.Path, params.Encode()) + //delete empty param key + for key, val := range params { + if len(val) > 0 && val[0] == "" { + params.Del(key) + } + } + + s, _ := url.QueryUnescape(params.Encode()) + rawURL = fmt.Sprintf("%s://%s%s?%s", c.Protocol, host, c.Path, s) + // Print your own registration service providers. dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), (common.RoleType(common.PROVIDER)).String()) logger.Debugf("provider path:%s, url:%s", dubboPath, rawURL) @@ -334,7 +342,7 @@ func (r *BaseRegistry) providerRegistry(c common.URL, params url.Values, f creat } // consumerRegistry for consumer role do -func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values, f createPathFunc) (string, string, error) { +func (r *BaseRegistry) consumerRegistry(c *common.URL, params url.Values, f createPathFunc) (string, string, error) { var ( dubboPath string rawURL string @@ -361,7 +369,8 @@ func (r *BaseRegistry) consumerRegistry(c common.URL, params url.Values, f creat } params.Add("protocol", c.Protocol) - rawURL = fmt.Sprintf("consumer://%s%s?%s", localIP, c.Path, params.Encode()) + s, _ := url.QueryUnescape(params.Encode()) + rawURL = fmt.Sprintf("consumer://%s%s?%s", localIP, c.Path, s) dubboPath = fmt.Sprintf("/dubbo/%s/%s", r.service(c), (common.RoleType(common.CONSUMER)).String()) logger.Debugf("consumer path:%s, url:%s", dubboPath, rawURL) diff --git a/registry/consul/listener.go b/registry/consul/listener.go index b1598345032bddd7358c2263e5fd71b3d4e5545d..0d665afe5a8425bd91a02b74cfa451405974e21a 100644 --- a/registry/consul/listener.go +++ b/registry/consul/listener.go @@ -39,17 +39,17 @@ import ( // registry. type consulListener struct { // Registry url. - registryUrl common.URL + registryUrl *common.URL // Consumer url. - consumerUrl common.URL + consumerUrl *common.URL // Consul watcher. plan *watch.Plan // Most recent service urls return by // watcher. - urls []common.URL + urls []*common.URL // All service information changes will // be wrapped into ServiceEvent, and be @@ -78,7 +78,7 @@ type consulListener struct { wg sync.WaitGroup } -func newConsulListener(registryUrl common.URL, consumerUrl common.URL) (*consulListener, error) { +func newConsulListener(registryUrl *common.URL, consumerUrl *common.URL) (*consulListener, error) { params := make(map[string]interface{}, 8) params["type"] = "service" params["service"] = consumerUrl.Service() @@ -93,7 +93,7 @@ func newConsulListener(registryUrl common.URL, consumerUrl common.URL) (*consulL registryUrl: registryUrl, consumerUrl: consumerUrl, plan: plan, - urls: make([]common.URL, 0, 8), + urls: make([]*common.URL, 0, 8), eventCh: make(chan *registry.ServiceEvent, 32), errCh: make(chan error, 32), done: make(chan struct{}), @@ -142,7 +142,7 @@ func (l *consulListener) run() { func (l *consulListener) handler(idx uint64, raw interface{}) { var ( service *consul.ServiceEntry - url common.URL + url *common.URL ok bool err error ) @@ -153,7 +153,7 @@ func (l *consulListener) handler(idx uint64, raw interface{}) { l.errCh <- err return } - newUrls := make([]common.URL, 0, 8) + newUrls := make([]*common.URL, 0, 8) events := make([]*registry.ServiceEvent, 0, 8) for _, service = range services { diff --git a/registry/consul/registry.go b/registry/consul/registry.go index c425c5ec20d36be02c00499340f13b13c9aa2655..0b7ba9758952b072d579ae9424e2c385e59a4378 100644 --- a/registry/consul/registry.go +++ b/registry/consul/registry.go @@ -36,7 +36,8 @@ import ( ) const ( - registryConnDelay = 3 + registryConnDelay = 3 + registryDestroyDefaultTimeout = time.Second * 3 ) func init() { @@ -75,7 +76,7 @@ func newConsulRegistry(url *common.URL) (registry.Registry, error) { // Register register @url // it delegate the job to register() method -func (r *consulRegistry) Register(url common.URL) error { +func (r *consulRegistry) Register(url *common.URL) error { var err error role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) @@ -89,7 +90,7 @@ func (r *consulRegistry) Register(url common.URL) error { } // register actually register the @url -func (r *consulRegistry) register(url common.URL) error { +func (r *consulRegistry) register(url *common.URL) error { service, err := buildService(url) if err != nil { return err @@ -99,7 +100,7 @@ func (r *consulRegistry) register(url common.URL) error { // UnRegister unregister the @url // it delegate the job to unregister() method -func (r *consulRegistry) UnRegister(url common.URL) error { +func (r *consulRegistry) UnRegister(url *common.URL) error { var err error role, _ := strconv.Atoi(r.URL.GetParam(constant.ROLE_KEY, "")) @@ -113,7 +114,7 @@ func (r *consulRegistry) UnRegister(url common.URL) error { } // unregister actually unregister the @url -func (r *consulRegistry) unregister(url common.URL) error { +func (r *consulRegistry) unregister(url *common.URL) error { return r.client.Agent().ServiceDeregister(buildId(url)) } @@ -140,7 +141,7 @@ func (r *consulRegistry) subscribe(url *common.URL, notifyListener registry.Noti return } - listener, err := r.getListener(*url) + listener, err := r.getListener(url) if err != nil { if !r.IsAvailable() { logger.Warnf("event listener game over.") @@ -165,14 +166,14 @@ func (r *consulRegistry) subscribe(url *common.URL, notifyListener registry.Noti } } -func (r *consulRegistry) getListener(url common.URL) (registry.Listener, error) { - listener, err := newConsulListener(*r.URL, url) +func (r *consulRegistry) getListener(url *common.URL) (registry.Listener, error) { + listener, err := newConsulListener(r.URL, url) return listener, err } // GetUrl get registry URL of consul registry center -func (r *consulRegistry) GetUrl() common.URL { - return *r.URL +func (r *consulRegistry) GetUrl() *common.URL { + return r.URL } // IsAvailable checks consul registry center whether is available @@ -187,5 +188,25 @@ func (r *consulRegistry) IsAvailable() bool { // Destroy consul registry center func (r *consulRegistry) Destroy() { + if r.URL != nil { + done := make(chan struct{}, 1) + go func() { + defer func() { + if e := recover(); e != nil { + logger.Errorf("consulRegistry destory with panic: %v", e) + } + done <- struct{}{} + }() + if err := r.UnRegister(r.URL); err != nil { + logger.Errorf("consul registry unregister with err: %s", err.Error()) + } + }() + select { + case <-done: + logger.Infof("consulRegistry unregister done") + case <-time.After(registryDestroyDefaultTimeout): + logger.Errorf("consul unregister timeout") + } + } close(r.done) } diff --git a/registry/consul/registry_test.go b/registry/consul/registry_test.go index 94718f5ab657c198882f065a50e5d5a2c9d4bc6f..b300f7536ddf35f2a4d062900b1f3e4eda33f25d 100644 --- a/registry/consul/registry_test.go +++ b/registry/consul/registry_test.go @@ -55,3 +55,19 @@ func (suite *consulRegistryTestSuite) testSubscribe() { assert.NoError(suite.t, err) suite.listener = listener } + +func (suite *consulRegistryTestSuite) testDestroy() { + consumerRegistryUrl := newConsumerRegistryUrl(registryHost, registryPort) + consumerRegistry, _ := newConsulRegistry(consumerRegistryUrl) + consulRegistryImp := consumerRegistry.(*consulRegistry) + assert.True(suite.t, consulRegistryImp.IsAvailable()) + consulRegistryImp.Destroy() + assert.False(suite.t, consulRegistryImp.IsAvailable()) + + consumerRegistry, _ = newConsulRegistry(consumerRegistryUrl) + consulRegistryImp = consumerRegistry.(*consulRegistry) + consulRegistryImp.URL = nil + assert.True(suite.t, consulRegistryImp.IsAvailable()) + consulRegistryImp.Destroy() + assert.False(suite.t, consulRegistryImp.IsAvailable()) +} diff --git a/registry/consul/service_discovery.go b/registry/consul/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..d8ab93f31ee6fdcf79aa869a35548e8192841fe4 --- /dev/null +++ b/registry/consul/service_discovery.go @@ -0,0 +1,498 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package consul + +import ( + "encoding/base64" + "fmt" + "strconv" + "sync" + "time" +) + +import ( + "github.com/dubbogo/gost/container/set" + "github.com/dubbogo/gost/page" + consul "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/api/watch" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/registry" +) + +const ( + enable = "enable" + watch_type = "type" + watch_type_service = "service" + watch_service = "service" + watch_passingonly = "passingonly" + watch_passingonly_true = true +) + +var ( + errConsulClientClosed = perrors.New("consul client is closed") +) + +// init will put the service discovery into extension +func init() { + extension.SetServiceDiscovery(constant.CONSUL_KEY, newConsulServiceDiscovery) +} + +// consulServiceDiscovery is the implementation of service discovery based on consul. +type consulServiceDiscovery struct { + // descriptor is a short string about the basic information of this instance + descriptor string + clientLock sync.RWMutex + // Consul client. + consulClient *consul.Client + checkPassInterval int64 + tag string + address string + deregisterCriticalServiceAfter string + ttl sync.Map + *consul.Config +} + +// newConsulServiceDiscovery will create new service discovery instance +// use double-check pattern to reduce race condition +func newConsulServiceDiscovery(name string) (registry.ServiceDiscovery, error) { + 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) + } + + descriptor := fmt.Sprintf("consul-service-discovery[%s]", remoteConfig.Address) + + config := &consul.Config{Address: remoteConfig.Address, Token: remoteConfig.Params[constant.ACL_TOKEN]} + client, err := consul.NewClient(config) + if err != nil { + return nil, perrors.WithMessage(err, "create consul client failed.") + } + + return &consulServiceDiscovery{ + address: remoteConfig.Address, + descriptor: descriptor, + checkPassInterval: getCheckPassInterval(remoteConfig.Params), + Config: config, + tag: remoteConfig.Params[constant.QUERY_TAG], + consulClient: client, + deregisterCriticalServiceAfter: getDeregisterAfter(remoteConfig.Params), + clientLock: sync.RWMutex{}, + }, nil +} + +func (csd *consulServiceDiscovery) String() string { + return csd.descriptor +} + +// nolint +func (csd *consulServiceDiscovery) getConsulClient() *consul.Client { + csd.clientLock.RLock() + defer csd.clientLock.RUnlock() + return csd.consulClient +} + +// nolint +func (csd *consulServiceDiscovery) setConsulClient(consulClient *consul.Client) { + csd.clientLock.Lock() + defer csd.clientLock.Unlock() + csd.consulClient = consulClient +} + +func (csd *consulServiceDiscovery) Destroy() error { + csd.setConsulClient(nil) + csd.ttl.Range(func(key, t interface{}) bool { + close(t.(chan struct{})) + csd.ttl.Delete(key) + return true + }) + return nil +} + +func (csd *consulServiceDiscovery) Register(instance registry.ServiceInstance) error { + var ( + err error + consulClient *consul.Client + ) + ins, _ := csd.buildRegisterInstance(instance) + if consulClient = csd.getConsulClient(); consulClient == nil { + return errConsulClientClosed + } + err = consulClient.Agent().ServiceRegister(ins) + if err != nil { + logger.Errorf("consul register the instance %s fail:%v", instance.GetServiceName(), err) + return perrors.WithMessage(err, "consul could not register the instance. "+instance.GetServiceName()) + } + + return csd.registerTtl(instance) +} + +func (csd *consulServiceDiscovery) registerTtl(instance registry.ServiceInstance) error { + var ( + err error + consulClient *consul.Client + ) + + checkID := buildID(instance) + + stopChan := make(chan struct{}) + csd.ttl.LoadOrStore(buildID(instance), stopChan) + + period := time.Duration(csd.checkPassInterval/8) * time.Millisecond + timer := time.NewTicker(period) + go func() { + defer timer.Stop() + for { + select { + case <-timer.C: + if consulClient = csd.getConsulClient(); consulClient == nil { + logger.Debugf("consul client is closed!") + return + } + err = consulClient.Agent().PassTTL(fmt.Sprintf("service:%s", checkID), "") + if err != nil { + logger.Warnf("pass ttl heartbeat fail:%v", err) + break + } + logger.Debugf("passed ttl heartbeat for %s", checkID) + break + case <-stopChan: + logger.Info("ttl %s for service %s is stopped", checkID, instance.GetServiceName()) + return + } + } + }() + return nil +} + +func (csd *consulServiceDiscovery) Update(instance registry.ServiceInstance) error { + var ( + err error + consulClient *consul.Client + ) + ins, _ := csd.buildRegisterInstance(instance) + consulClient = csd.getConsulClient() + if consulClient == nil { + return errConsulClientClosed + } + err = consulClient.Agent().ServiceDeregister(buildID(instance)) + if err != nil { + logger.Warnf("unregister instance %s fail:%v", instance.GetServiceName(), err) + } + return consulClient.Agent().ServiceRegister(ins) +} + +func (csd *consulServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + var ( + err error + consulClient *consul.Client + ) + if consulClient = csd.getConsulClient(); consulClient == nil { + return errConsulClientClosed + } + err = consulClient.Agent().ServiceDeregister(buildID(instance)) + if err != nil { + logger.Errorf("unregister service instance %s,error: %v", instance.GetId(), err) + return err + } + stopChanel, ok := csd.ttl.Load(buildID(instance)) + if !ok { + logger.Warnf("ttl for service instance %s didn't exist", instance.GetId()) + return nil + } + close(stopChanel.(chan struct{})) + csd.ttl.Delete(buildID(instance)) + return nil +} + +func (csd *consulServiceDiscovery) GetDefaultPageSize() int { + return registry.DefaultPageSize +} + +func (csd *consulServiceDiscovery) GetServices() *gxset.HashSet { + var ( + err error + consulClient *consul.Client + services map[string][]string + ) + var res = gxset.NewSet() + if consulClient = csd.getConsulClient(); consulClient == nil { + logger.Warnf("consul client is closed!") + return res + } + services, _, err = consulClient.Catalog().Services(nil) + if err != nil { + logger.Errorf("get services,error: %v", err) + return res + } + + for service, _ := range services { + res.Add(service) + } + return res + +} + +// encodeConsulMetadata because consul validate key strictly. +func encodeConsulMetadata(metadata map[string]string) map[string]string { + consulMetadata := make(map[string]string, len(metadata)) + encoder := base64.RawStdEncoding + for k, v := range metadata { + consulMetadata[encoder.EncodeToString([]byte(k))] = v + } + return consulMetadata +} + +// nolint +func decodeConsulMetadata(metadata map[string]string) map[string]string { + dubboMetadata := make(map[string]string, len(metadata)) + encoder := base64.RawStdEncoding + for k, v := range metadata { + kBytes, err := encoder.DecodeString(k) + if err != nil { + logger.Warnf("can not decoded consul metadata key %s", k) + continue + } + dubboMetadata[string(kBytes)] = v + } + return dubboMetadata +} + +func (csd *consulServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + var ( + err error + consulClient *consul.Client + instances []*consul.ServiceEntry + ) + if consulClient = csd.getConsulClient(); consulClient == nil { + logger.Warn("consul client is closed!") + return nil + } + instances, _, err = consulClient.Health().Service(serviceName, csd.tag, true, &consul.QueryOptions{ + WaitTime: time.Duration(csd.checkPassInterval), + }) + + if err != nil { + logger.Errorf("get instances for service %s,error: %v", serviceName, err) + return nil + } + + res := make([]registry.ServiceInstance, 0, len(instances)) + for _, ins := range instances { + metadata := ins.Service.Meta + + // enable status + enableStr := metadata[enable] + delete(metadata, enable) + enable, _ := strconv.ParseBool(enableStr) + metadata = decodeConsulMetadata(metadata) + + // health status + status := ins.Checks.AggregatedStatus() + healthy := false + if status == consul.HealthPassing { + healthy = true + } + res = append(res, ®istry.DefaultServiceInstance{ + Id: ins.Service.ID, + ServiceName: ins.Service.Service, + Host: ins.Service.Address, + Port: ins.Service.Port, + Enable: enable, + Healthy: healthy, + Metadata: metadata, + }) + } + + return res +} + +func (csd *consulServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + all := csd.GetInstances(serviceName) + res := make([]interface{}, 0, pageSize) + for i := offset; i < len(all) && i < offset+pageSize; i++ { + res = append(res, all[i]) + } + return gxpage.New(offset, pageSize, res, len(all)) +} + +func (csd *consulServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { + all := csd.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)) +} + +func (csd *consulServiceDiscovery) 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] = csd.GetInstancesByPage(name, offset, requestedSize) + } + return res +} + +func (csd *consulServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + + params := make(map[string]interface{}, 8) + params[watch_type] = watch_type_service + params[watch_service] = listener.ServiceName + params[watch_passingonly] = watch_passingonly_true + plan, err := watch.Parse(params) + if err != nil { + logger.Errorf("add listener for service %s,error:%v", listener.ServiceName, err) + return err + } + + plan.Handler = func(idx uint64, raw interface{}) { + services, ok := raw.([]*consul.ServiceEntry) + if !ok { + err = perrors.New("handler get non ServiceEntry type parameter") + return + } + instances := make([]registry.ServiceInstance, 0, len(services)) + for _, ins := range services { + metadata := ins.Service.Meta + + // enable status + enableStr := metadata[enable] + delete(metadata, enable) + enable, _ := strconv.ParseBool(enableStr) + + // health status + status := ins.Checks.AggregatedStatus() + healthy := false + if status == consul.HealthPassing { + healthy = true + } + instances = append(instances, ®istry.DefaultServiceInstance{ + Id: ins.Service.ID, + ServiceName: ins.Service.Service, + Host: ins.Service.Address, + Port: ins.Service.Port, + Enable: enable, + Healthy: healthy, + Metadata: metadata, + }) + } + e := csd.DispatchEventForInstances(listener.ServiceName, instances) + if e != nil { + logger.Errorf("Dispatching event got exception, service name: %s, err: %v", listener.ServiceName, err) + } + } + go func() { + err = plan.RunWithConfig(csd.Config.Address, csd.Config) + if err != nil { + logger.Error("consul plan run failure!error:%v", err) + } + }() + return nil +} + +func (csd *consulServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return csd.DispatchEventForInstances(serviceName, csd.GetInstances(serviceName)) +} + +func (csd *consulServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { + return csd.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances)) +} + +func (csd *consulServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + extension.GetGlobalDispatcher().Dispatch(event) + return nil +} + +func (csd *consulServiceDiscovery) buildRegisterInstance(instance registry.ServiceInstance) (*consul.AgentServiceRegistration, error) { + metadata := instance.GetMetadata() + metadata = encodeConsulMetadata(metadata) + metadata[enable] = strconv.FormatBool(instance.IsEnable()) + // check + check := csd.buildCheck(instance) + + return &consul.AgentServiceRegistration{ + ID: buildID(instance), + Name: instance.GetServiceName(), + Port: instance.GetPort(), + Address: instance.GetHost(), + Meta: metadata, + Check: &check, + }, nil +} + +func (csd *consulServiceDiscovery) buildCheck(instance registry.ServiceInstance) consul.AgentServiceCheck { + + deregister, ok := instance.GetMetadata()[constant.DEREGISTER_AFTER] + if !ok || len(deregister) == 0 { + deregister = constant.DEFAULT_DEREGISTER_TIME + } + return consul.AgentServiceCheck{ + TTL: strconv.FormatInt(csd.checkPassInterval/1000, 10) + "s", + DeregisterCriticalServiceAfter: csd.deregisterCriticalServiceAfter, + } +} + +// nolint +func getCheckPassInterval(params map[string]string) int64 { + checkPassIntervalStr, ok := params[constant.CHECK_PASS_INTERVAL] + if !ok { + return constant.DEFAULT_CHECK_PASS_INTERVAL + } + checkPassInterval, err := strconv.ParseInt(checkPassIntervalStr, 10, 64) + if err != nil { + logger.Warnf("consul service discovery remote config error:%s", checkPassIntervalStr) + return constant.DEFAULT_CHECK_PASS_INTERVAL + } + return checkPassInterval +} + +// nolint +func getDeregisterAfter(metadata map[string]string) string { + deregister, ok := metadata[constant.DEREGISTER_AFTER] + if !ok || len(deregister) == 0 { + deregister = constant.DEFAULT_DEREGISTER_TIME + } + return deregister +} + +// nolint +func buildID(instance registry.ServiceInstance) string { + id := fmt.Sprintf("id:%s,serviceName:%s,host:%s,port:%d", instance.GetId(), instance.GetServiceName(), instance.GetHost(), instance.GetPort()) + return id +} diff --git a/registry/consul/service_discovery_test.go b/registry/consul/service_discovery_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2169857ee8f79a92322234a0b17a4d7122a0d975 --- /dev/null +++ b/registry/consul/service_discovery_test.go @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package consul + +import ( + "fmt" + "math/rand" + "strconv" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/observer" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/registry" + "github.com/apache/dubbo-go/remoting/consul" +) + +var ( + testName = "test" + consulCheckPassInterval = 17000 + consulDeregisterCriticalServiceAfter = "20s" + consulWatchTimeout = 60000 +) + +func TestConsulServiceDiscovery_newConsulServiceDiscovery(t *testing.T) { + name := "consul1" + _, err := newConsulServiceDiscovery(name) + assert.NotNil(t, err) + + sdc := &config.ServiceDiscoveryConfig{ + Protocol: "consul", + RemoteRef: "mock", + } + + config.GetBaseConfig().ServiceDiscoveries[name] = sdc + + _, err = newConsulServiceDiscovery(name) + assert.NotNil(t, err) + + config.GetBaseConfig().Remotes["mock"] = &config.RemoteConfig{ + Address: "localhost:8081", + } + + res, err := newConsulServiceDiscovery(name) + assert.Nil(t, err) + assert.NotNil(t, res) +} + +func TestConsulServiceDiscovery_Destroy(t *testing.T) { + prepareData() + serviceDiscovery, err := extension.GetServiceDiscovery(constant.CONSUL_KEY, testName) + prepareService() + assert.Nil(t, err) + assert.NotNil(t, serviceDiscovery) + err = serviceDiscovery.Destroy() + assert.Nil(t, err) + assert.Nil(t, serviceDiscovery.(*consulServiceDiscovery).consulClient) +} + +func TestConsulServiceDiscovery_CRUD(t *testing.T) { + // start consul agent + consulAgent := consul.NewConsulAgent(t, registryPort) + defer consulAgent.Shutdown() + + prepareData() + var eventDispatcher = MockEventDispatcher{Notify: make(chan struct{}, 1)} + extension.SetEventDispatcher("mock", func() observer.EventDispatcher { + return &eventDispatcher + }) + + extension.SetAndInitGlobalDispatcher("mock") + rand.Seed(time.Now().Unix()) + + instance, _ := prepareService() + + // clean data + serviceDiscovery, err := extension.GetServiceDiscovery(constant.CONSUL_KEY, testName) + assert.Nil(t, err) + + err = serviceDiscovery.Unregister(instance) + assert.Nil(t, err) + + err = serviceDiscovery.Register(instance) + assert.Nil(t, err) + + //sometimes nacos may be failed to push update of instance, + //so it need 10s to pull, we sleep 10 second to make sure instance has been update + time.Sleep(3 * time.Second) + page := serviceDiscovery.GetHealthyInstancesByPage(instance.GetServiceName(), 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()) + + instanceResult := page.GetData()[0].(*registry.DefaultServiceInstance) + assert.NotNil(t, instanceResult) + assert.Equal(t, buildID(instance), instanceResult.GetId()) + assert.Equal(t, instance.GetHost(), instanceResult.GetHost()) + assert.Equal(t, instance.GetPort(), instanceResult.GetPort()) + assert.Equal(t, instance.GetServiceName(), instanceResult.GetServiceName()) + metadata := instanceResult.GetMetadata() + assert.Equal(t, 0, len(metadata)) + + instance.GetMetadata()["aaa"] = "bbb" + err = serviceDiscovery.Update(instance) + assert.Nil(t, err) + + time.Sleep(3 * time.Second) + pageMap := serviceDiscovery.GetRequestInstances([]string{instance.GetServiceName()}, 0, 1) + assert.Equal(t, 1, len(pageMap)) + + page = pageMap[instance.GetServiceName()] + assert.NotNil(t, page) + assert.Equal(t, 1, len(page.GetData())) + + instanceResult = page.GetData()[0].(*registry.DefaultServiceInstance) + v, _ := instanceResult.Metadata["aaa"] + assert.Equal(t, "bbb", v) + + // test dispatcher event + //err = serviceDiscovery.DispatchEventByServiceName(instanceResult.GetServiceName()) + //assert.Nil(t, err) + + // test AddListener + err = serviceDiscovery.AddListener(®istry.ServiceInstancesChangedListener{ServiceName: instance.GetServiceName()}) + assert.Nil(t, err) + err = serviceDiscovery.Unregister(instance) + assert.Nil(t, err) + timer := time.NewTimer(time.Second * 10) + select { + case <-eventDispatcher.Notify: + assert.NotNil(t, eventDispatcher.Event) + break + case <-timer.C: + assert.Fail(t, "") + break + } +} + +func prepareData() { + config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{ + Protocol: "consul", + RemoteRef: testName, + } + + config.GetBaseConfig().Remotes[testName] = &config.RemoteConfig{ + Address: fmt.Sprintf("%s:%d", registryHost, registryPort), + } +} + +func prepareService() (registry.ServiceInstance, *common.URL) { + id := "id" + + registryUrl, _ := common.NewURL(protocol + "://" + providerHost + ":" + strconv.Itoa(providerPort) + "/" + service + "?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&consul-check-pass-interval=" + strconv.Itoa(consulCheckPassInterval) + "&consul-deregister-critical-service-after=" + consulDeregisterCriticalServiceAfter + "&" + + "consul-watch-timeout=" + strconv.Itoa(consulWatchTimeout)) + + return ®istry.DefaultServiceInstance{ + Id: id, + ServiceName: service, + Host: registryHost, + Port: registryPort, + Enable: true, + Healthy: true, + Metadata: nil, + }, registryUrl +} + +type MockEventDispatcher struct { + Notify chan struct{} + Event observer.Event +} + +// AddEventListener do nothing +func (m *MockEventDispatcher) AddEventListener(observer.EventListener) { +} + +// AddEventListeners do nothing +func (m *MockEventDispatcher) AddEventListeners([]observer.EventListener) { +} + +// RemoveEventListener do nothing +func (m *MockEventDispatcher) RemoveEventListener(observer.EventListener) { +} + +// RemoveEventListeners do nothing +func (m *MockEventDispatcher) RemoveEventListeners([]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) { + m.Event = event + m.Notify <- struct{}{} +} diff --git a/registry/consul/utils.go b/registry/consul/utils.go index 05ac5e76e292a2b574bd5e661bbbcca4f419fc22..76f5d2dd3378f67d300d10b4c5ecc72934ef7715 100644 --- a/registry/consul/utils.go +++ b/registry/consul/utils.go @@ -25,7 +25,6 @@ import ( ) import ( - gxnet "github.com/dubbogo/gost/net" consul "github.com/hashicorp/consul/api" perrors "github.com/pkg/errors" ) @@ -34,12 +33,12 @@ import ( "github.com/apache/dubbo-go/common" ) -func buildId(url common.URL) string { +func buildId(url *common.URL) string { t := md5.Sum([]byte(url.String())) return hex.EncodeToString(t[:]) } -func buildService(url common.URL) (*consul.AgentServiceRegistration, error) { +func buildService(url *common.URL) (*consul.AgentServiceRegistration, error) { var err error // id @@ -47,7 +46,7 @@ func buildService(url common.URL) (*consul.AgentServiceRegistration, error) { // address if url.Ip == "" { - url.Ip, _ = gxnet.GetLocalIP() + url.Ip = common.GetLocalIp() } // port @@ -94,21 +93,21 @@ func buildService(url common.URL) (*consul.AgentServiceRegistration, error) { return service, nil } -func retrieveURL(service *consul.ServiceEntry) (common.URL, error) { +func retrieveURL(service *consul.ServiceEntry) (*common.URL, error) { url, ok := service.Service.Meta["url"] if !ok { - return common.URL{}, perrors.New("retrieve url fails with no url key in service meta") + return nil, perrors.New("retrieve url fails with no url key in service meta") } url1, err := common.NewURL(url) if err != nil { - return common.URL{}, perrors.WithStack(err) + return nil, perrors.WithStack(err) } return url1, nil } -func in(url common.URL, urls []common.URL) bool { +func in(url *common.URL, urls []*common.URL) bool { for _, url1 := range urls { - if url.URLEqual(url1) { + if common.IsEquals(url, url1) { return true } } diff --git a/registry/consul/utils_test.go b/registry/consul/utils_test.go index 939352dc088faa2c32be8173d0aa6f4516dfe503..d78c534e931f9aa3e0220bb08aa29222220ce619 100644 --- a/registry/consul/utils_test.go +++ b/registry/consul/utils_test.go @@ -63,8 +63,8 @@ func newConsumerRegistryUrl(host string, port int) *common.URL { ) } -func newProviderUrl(host string, port int, service string, protocol string) common.URL { - return *common.NewURLWithOptions( +func newProviderUrl(host string, port int, service string, protocol string) *common.URL { + return common.NewURLWithOptions( common.WithIp(host), common.WithPort(strconv.Itoa(port)), common.WithPath(service), @@ -72,8 +72,8 @@ func newProviderUrl(host string, port int, service string, protocol string) comm ) } -func newConsumerUrl(host string, port int, service string, protocol string) common.URL { - return *common.NewURLWithOptions( +func newConsumerUrl(host string, port int, service string, protocol string) *common.URL { + return common.NewURLWithOptions( common.WithIp(host), common.WithPort(strconv.Itoa(port)), common.WithPath(service), @@ -130,8 +130,8 @@ type consulRegistryTestSuite struct { providerRegistry registry.Registry consumerRegistry *consulRegistry listener registry.Listener - providerUrl common.URL - consumerUrl common.URL + providerUrl *common.URL + consumerUrl *common.URL } func newConsulRegistryTestSuite(t *testing.T) *consulRegistryTestSuite { @@ -163,6 +163,7 @@ func test1(t *testing.T) { suite.testListener(remoting.EventTypeAdd) suite.testUnregister() suite.testListener(remoting.EventTypeDel) + suite.testDestroy() } // subscribe -> register -> unregister @@ -183,6 +184,7 @@ func test2(t *testing.T) { suite.testListener(remoting.EventTypeAdd) suite.testUnregister() suite.testListener(remoting.EventTypeDel) + suite.testDestroy() } func TestConsulRegistry(t *testing.T) { diff --git a/registry/directory/directory.go b/registry/directory/directory.go index 2fbf9410f76c473362964c9ef148e3c581d3d045..ed1d8fb3cd310f4c0fd67d3d80f2c8ffe931ffaa 100644 --- a/registry/directory/directory.go +++ b/registry/directory/directory.go @@ -18,6 +18,9 @@ package directory import ( + "fmt" + "net/url" + "os" "sync" ) @@ -29,6 +32,7 @@ import ( import ( "github.com/apache/dubbo-go/cluster" "github.com/apache/dubbo-go/cluster/directory" + "github.com/apache/dubbo-go/cluster/router/chain" "github.com/apache/dubbo-go/common" "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/extension" @@ -55,12 +59,14 @@ type RegistryDirectory struct { serviceType string registry registry.Registry cacheInvokersMap *sync.Map // use sync.map + consumerURL *common.URL cacheOriginUrl *common.URL configurators []config_center.Configurator consumerConfigurationListener *consumerConfigurationListener referenceConfigurationListener *referenceConfigurationListener serviceKey string forbidden atomic.Bool + registerLock sync.Mutex // this lock if for register } // NewRegistryDirectory will create a new RegistryDirectory @@ -68,6 +74,7 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. if url.SubURL == nil { return nil, perrors.Errorf("url is invalid, suburl can not be nil") } + logger.Debugf("new RegistryDirectory for service :%s.", url.Key()) dir := &RegistryDirectory{ BaseDirectory: directory.NewBaseDirectory(url), cacheInvokers: []protocol.Invoker{}, @@ -75,6 +82,15 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. serviceType: url.SubURL.Service(), registry: registry, } + + dir.consumerURL = dir.getConsumerUrl(url.SubURL) + + if routerChain, err := chain.NewRouterChain(dir.consumerURL); err == nil { + dir.BaseDirectory.SetRouterChain(routerChain) + } else { + logger.Warnf("fail to create router chain with url: %s, err is: %v", url.SubURL, err) + } + dir.consumerConfigurationListener = newConsumerConfigurationListener(dir) go dir.subscribe(url.SubURL) @@ -83,6 +99,7 @@ func NewRegistryDirectory(url *common.URL, registry registry.Registry) (cluster. // subscribe from registry func (dir *RegistryDirectory) subscribe(url *common.URL) { + logger.Debugf("subscribe service :%s for RegistryDirectory.", url.Key()) dir.consumerConfigurationListener.addNotifyListener(dir) dir.referenceConfigurationListener = newReferenceConfigurationListener(dir, url) dir.registry.Subscribe(url, dir) @@ -90,68 +107,177 @@ func (dir *RegistryDirectory) subscribe(url *common.URL) { // Notify monitor changes from registry,and update the cacheServices func (dir *RegistryDirectory) Notify(event *registry.ServiceEvent) { - go dir.update(event) + if event == nil { + return + } + go dir.refreshInvokers(event) } -// update the cacheServices and subscribe service from registry -func (dir *RegistryDirectory) update(res *registry.ServiceEvent) { - if res == nil { - return +// NotifyAll notify the events that are complete Service Event List. +// After notify the address, the callback func will be invoked. +func (dir *RegistryDirectory) NotifyAll(events []*registry.ServiceEvent, callback func()) { + go dir.refreshAllInvokers(events, callback) +} + +// refreshInvokers refreshes service's events. +func (dir *RegistryDirectory) refreshInvokers(event *registry.ServiceEvent) { + if event != nil { + logger.Debugf("refresh invokers with %+v", event) + } else { + logger.Debug("refresh invokers with nil") + } + + var oldInvoker protocol.Invoker + if event != nil { + oldInvoker, _ = dir.cacheInvokerByEvent(event) + } + dir.setNewInvokers() + if oldInvoker != nil { + oldInvoker.Destroy() } - logger.Debugf("registry update, result{%s}", res) - logger.Debugf("update service name: %s!", res.Service) - dir.refreshInvokers(res) } -func (dir *RegistryDirectory) refreshInvokers(res *registry.ServiceEvent) { +// refreshAllInvokers the argument is the complete list of the service events, we can safely assume any cached invoker +// not in the incoming list can be removed. The Action of serviceEvent should be EventTypeUpdate. +func (dir *RegistryDirectory) refreshAllInvokers(events []*registry.ServiceEvent, callback func()) { var ( - url *common.URL - oldInvoker protocol.Invoker = nil + oldInvokers []protocol.Invoker + addEvents []*registry.ServiceEvent ) - // judge is override or others - if res != nil { - url = &res.Service - // 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 - url.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY { - url = nil + dir.overrideUrl(dir.GetDirectoryUrl()) + referenceUrl := dir.GetDirectoryUrl().SubURL + // loop the events to check the Action should be EventTypeUpdate. + for _, event := range events { + if event.Action != remoting.EventTypeUpdate { + panic("Your implements of register center is wrong, " + + "please check the Action of ServiceEvent should be EventTypeUpdate") + return } - switch res.Action { - case remoting.EventTypeAdd, remoting.EventTypeUpdate: - logger.Infof("selector add service url{%s}", res.Service) - - var urls []*common.URL - for _, v := range config.GetRouterURLSet().Values() { - urls = append(urls, v.(*common.URL)) + // Originally it will Merge URL many times, now we just execute once. + // MergeUrl is executed once and put the result into Event. After this, the key will get from Event.Key(). + newUrl := dir.convertUrl(event) + newUrl = common.MergeUrl(newUrl, referenceUrl) + dir.overrideUrl(newUrl) + event.Update(newUrl) + } + // After notify all addresses, do some callback. + defer callback() + func() { + // this lock is work at batch update of InvokeCache + dir.registerLock.Lock() + defer dir.registerLock.Unlock() + // get need clear invokers from original invoker list + dir.cacheInvokersMap.Range(func(k, v interface{}) bool { + if !dir.eventMatched(k.(string), events) { + // delete unused invoker from cache + if invoker := dir.uncacheInvokerWithKey(k.(string)); invoker != nil { + oldInvokers = append(oldInvokers, invoker) + } } - - if len(urls) > 0 { - dir.SetRouters(urls) + return true + }) + // get need add invokers from events + for _, event := range events { + // Get the key from Event.Key() + if _, ok := dir.cacheInvokersMap.Load(event.Key()); !ok { + addEvents = append(addEvents, event) } - oldInvoker = dir.cacheInvoker(url) - case remoting.EventTypeDel: - oldInvoker = dir.uncacheInvoker(url) - logger.Infof("selector delete service url{%s}", res.Service) - default: - return } + // loop the updateEvents + for _, event := range addEvents { + logger.Debugf("registry update, result{%s}", event) + logger.Infof("selector add service url{%s}", event.Service) + // FIXME: routers are built in every address notification? + dir.configRouters() + if oldInvoker, _ := dir.doCacheInvoker(event.Service); oldInvoker != nil { + oldInvokers = append(oldInvokers, oldInvoker) + } + } + }() + dir.setNewInvokers() + // destroy unused invokers + for _, invoker := range oldInvokers { + go invoker.Destroy() + } +} + +// eventMatched checks if a cached invoker appears in the incoming invoker list, if no, then it is safe to remove. +func (dir *RegistryDirectory) eventMatched(key string, events []*registry.ServiceEvent) bool { + for _, event := range events { + if dir.invokerCacheKey(event) == key { + return true + } + } + return false +} + +// invokerCacheKey generates the key in the cache for a given ServiceEvent. +func (dir *RegistryDirectory) invokerCacheKey(event *registry.ServiceEvent) string { + // If the url is merged, then return Event.Key() directly. + if event.Updated() { + return event.Key() } + referenceUrl := dir.GetDirectoryUrl().SubURL + newUrl := common.MergeUrl(event.Service, referenceUrl) + event.Update(newUrl) + return newUrl.Key() +} +// setNewInvokers groups the invokers from the cache first, then set the result to both directory and router chain. +func (dir *RegistryDirectory) setNewInvokers() { newInvokers := dir.toGroupInvokers() dir.listenerLock.Lock() + defer dir.listenerLock.Unlock() dir.cacheInvokers = newInvokers - dir.listenerLock.Unlock() - // After dir.cacheInvokers is updated,destroy the oldInvoker - // Ensure that no request will enter the oldInvoker - if oldInvoker != nil { - oldInvoker.Destroy() + dir.RouterChain().SetInvokers(newInvokers) +} + +// cacheInvokerByEvent caches invokers from the service event +func (dir *RegistryDirectory) cacheInvokerByEvent(event *registry.ServiceEvent) (protocol.Invoker, error) { + // judge is override or others + if event != nil { + u := dir.convertUrl(event) + switch event.Action { + case remoting.EventTypeAdd, remoting.EventTypeUpdate: + logger.Infof("selector add service url{%s}", event.Service) + // FIXME: routers are built in every address notification? + dir.configRouters() + return dir.cacheInvoker(u), nil + case remoting.EventTypeDel: + logger.Infof("selector delete service url{%s}", event.Service) + return dir.uncacheInvoker(u), nil + default: + return nil, fmt.Errorf("illegal event type: %v", event.Action) + } + } + return nil, nil +} + +// configRouters configures dynamic routers into the router chain, but, the current impl is incorrect, see FIXME above. +func (dir *RegistryDirectory) configRouters() { + var urls []*common.URL + for _, v := range config.GetRouterURLSet().Values() { + urls = append(urls, v.(*common.URL)) } + if len(urls) > 0 { + dir.SetRouters(urls) + } +} + +// convertUrl processes override:// and router:// +func (dir *RegistryDirectory) convertUrl(res *registry.ServiceEvent) *common.URL { + ret := res.Service + if ret.Protocol == constant.OVERRIDE_PROTOCOL || // 1.for override url in 2.6.x + ret.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.CONFIGURATORS_CATEGORY { + dir.configurators = append(dir.configurators, extension.GetDefaultConfigurator(ret)) + ret = nil + } else if ret.Protocol == constant.ROUTER_PROTOCOL || // 2.for router + ret.GetParam(constant.CATEGORY_KEY, constant.DEFAULT_CATEGORY) == constant.ROUTER_CATEGORY { + ret = nil + } + return ret } func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker { @@ -197,11 +323,15 @@ func (dir *RegistryDirectory) toGroupInvokers() []protocol.Invoker { return groupInvokersList } -// uncacheInvoker will return abandoned Invoker,if no Invoker to be abandoned,return nil +// 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()) + return dir.uncacheInvokerWithKey(url.Key()) +} + +func (dir *RegistryDirectory) uncacheInvokerWithKey(key string) protocol.Invoker { + logger.Debugf("service will be deleted in cache invokers: invokers key is %s!", key) + if cacheInvoker, ok := dir.cacheInvokersMap.Load(key); ok { + dir.cacheInvokersMap.Delete(key) return cacheInvoker.(protocol.Invoker) } return nil @@ -225,24 +355,38 @@ func (dir *RegistryDirectory) cacheInvoker(url *common.URL) protocol.Invoker { if url.Protocol == referenceUrl.Protocol || referenceUrl.Protocol == "" { newUrl := common.MergeUrl(url, referenceUrl) dir.overrideUrl(newUrl) - if cacheInvoker, ok := dir.cacheInvokersMap.Load(newUrl.Key()); !ok { - logger.Debugf("service will be added in cache invokers: invokers url is %s!", newUrl) - newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl) - if newInvoker != nil { - dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker) - } - } else { - logger.Debugf("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) - newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(*newUrl) - if newInvoker != nil { - dir.cacheInvokersMap.Store(newUrl.Key(), newInvoker) - return cacheInvoker.(protocol.Invoker) - } + if v, ok := dir.doCacheInvoker(newUrl); ok { + return v } } return nil } +func (dir *RegistryDirectory) doCacheInvoker(newUrl *common.URL) (protocol.Invoker, bool) { + key := newUrl.Key() + if cacheInvoker, ok := dir.cacheInvokersMap.Load(key); !ok { + logger.Debugf("service will be added in cache invokers: invokers url is %s!", newUrl) + newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(newUrl) + if newInvoker != nil { + dir.cacheInvokersMap.Store(key, newInvoker) + } + } else { + // if cached invoker has the same URL with the new URL, then no need to re-refer, and no need to destroy + // the old invoker. + if common.GetCompareURLEqualFunc()(newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) { + return nil, true + } + + logger.Debugf("service will be updated in cache invokers: new invoker url is %s, old invoker url is %s", newUrl, cacheInvoker.(protocol.Invoker).GetUrl()) + newInvoker := extension.GetProtocol(protocolwrapper.FILTER).Refer(newUrl) + if newInvoker != nil { + dir.cacheInvokersMap.Store(key, newInvoker) + return cacheInvoker.(protocol.Invoker), true + } + } + return nil, false +} + // List selected protocol invokers from the directory func (dir *RegistryDirectory) List(invocation protocol.Invocation) []protocol.Invoker { invokers := dir.cacheInvokers @@ -251,7 +395,7 @@ func (dir *RegistryDirectory) List(invocation protocol.Invocation) []protocol.In if routerChain == nil { return invokers } - return routerChain.Route(invokers, dir.cacheOriginUrl, invocation) + return routerChain.Route(dir.consumerURL, invocation) } // IsAvailable whether the directory is available @@ -287,6 +431,24 @@ func (dir *RegistryDirectory) overrideUrl(targetUrl *common.URL) { doOverrideUrl(dir.referenceConfigurationListener.Configurators(), targetUrl) } +func (dir *RegistryDirectory) getConsumerUrl(c *common.URL) *common.URL { + processID := fmt.Sprintf("%d", os.Getpid()) + localIP := common.GetLocalIp() + + params := url.Values{} + c.RangeParams(func(key, value string) bool { + params.Add(key, value) + return true + }) + + params.Add("pid", processID) + params.Add("ip", localIP) + params.Add("protocol", c.Protocol) + + return common.NewURLWithOptions(common.WithProtocol("consumer"), common.WithIp(localIP), common.WithPath(c.Path), + common.WithParams(params)) +} + func doOverrideUrl(configurators []config_center.Configurator, targetUrl *common.URL) { for _, v := range configurators { v.Configure(targetUrl) @@ -312,6 +474,7 @@ func newReferenceConfigurationListener(dir *RegistryDirectory, url *common.URL) // Process handle events and update Invokers func (l *referenceConfigurationListener) Process(event *config_center.ConfigChangeEvent) { l.BaseConfigurationListener.Process(event) + // FIXME: this doesn't trigger dir.overrideUrl() l.directory.refreshInvokers(nil) } @@ -338,5 +501,6 @@ func (l *consumerConfigurationListener) addNotifyListener(listener registry.Noti // Process handles events from Configuration Center and update Invokers func (l *consumerConfigurationListener) Process(event *config_center.ConfigChangeEvent) { l.BaseConfigurationListener.Process(event) + // FIXME: this doesn't trigger dir.overrideUrl() l.directory.refreshInvokers(nil) } diff --git a/registry/directory/directory_test.go b/registry/directory/directory_test.go index f2b2f8edd2d46950d2e74733b1d869e0de282ec0..b5d81eb0da0b7ae635b8a54e3002a9380a69a2fd 100644 --- a/registry/directory/directory_test.go +++ b/registry/directory/directory_test.go @@ -72,7 +72,7 @@ func TestSubscribe(t *testing.T) { func TestSubscribe_InvalidUrl(t *testing.T) { url, _ := common.NewURL("mock://127.0.0.1:1111") mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) - _, err := NewRegistryDirectory(&url, mockRegistry) + _, err := NewRegistryDirectory(url, mockRegistry) assert.Error(t, err) } @@ -83,9 +83,9 @@ func TestSubscribe_Group(t *testing.T) { regurl, _ := common.NewURL("mock://127.0.0.1:1111") suburl, _ := common.NewURL("dubbo://127.0.0.1:20000") suburl.SetParam(constant.CLUSTER_KEY, "mock") - regurl.SubURL = &suburl + regurl.SubURL = suburl mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) - dir, _ := NewRegistryDirectory(®url, mockRegistry) + dir, _ := NewRegistryDirectory(regurl, mockRegistry) go dir.(*RegistryDirectory).subscribe(common.NewURLWithOptions(common.WithPath("testservice"))) //for group1 @@ -93,7 +93,7 @@ func TestSubscribe_Group(t *testing.T) { urlmap.Set(constant.GROUP_KEY, "group1") urlmap.Set(constant.CLUSTER_KEY, "failover") //to test merge url for i := 0; i < 3; i++ { - mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: remoting.EventTypeAdd, Service: *common.NewURLWithOptions(common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"), + mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: remoting.EventTypeAdd, Service: common.NewURLWithOptions(common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"), common.WithParams(urlmap))}) } //for group2 @@ -101,7 +101,7 @@ func TestSubscribe_Group(t *testing.T) { urlmap2.Set(constant.GROUP_KEY, "group2") urlmap2.Set(constant.CLUSTER_KEY, "failover") //to test merge url for i := 0; i < 3; i++ { - mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: remoting.EventTypeAdd, Service: *common.NewURLWithOptions(common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"), + mockRegistry.(*registry.MockRegistry).MockEvent(®istry.ServiceEvent{Action: remoting.EventTypeAdd, Service: common.NewURLWithOptions(common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"), common.WithParams(urlmap2))}) } @@ -124,7 +124,7 @@ func Test_Destroy(t *testing.T) { func Test_List(t *testing.T) { registryDirectory, _ := normalRegistryDir() - time.Sleep(4e9) + time.Sleep(6e9) assert.Len(t, registryDirectory.List(&invocation.RPCInvocation{}), 3) assert.Equal(t, true, registryDirectory.IsAvailable()) @@ -192,6 +192,34 @@ func Test_toGroupInvokers(t *testing.T) { assert.Len(t, groupInvokers, 2) } +func Test_RefreshUrl(t *testing.T) { + registryDirectory, mockRegistry := normalRegistryDir() + providerUrl, _ := common.NewURL("dubbo://0.0.0.0:20011/org.apache.dubbo-go.mockService", + common.WithParamsValue(constant.CLUSTER_KEY, "mock1"), + common.WithParamsValue(constant.GROUP_KEY, "group"), + common.WithParamsValue(constant.VERSION_KEY, "1.0.0")) + providerUrl2, _ := common.NewURL("dubbo://0.0.0.0:20012/org.apache.dubbo-go.mockService", + common.WithParamsValue(constant.CLUSTER_KEY, "mock1"), + common.WithParamsValue(constant.GROUP_KEY, "group"), + common.WithParamsValue(constant.VERSION_KEY, "1.0.0")) + time.Sleep(1e9) + assert.Len(t, registryDirectory.cacheInvokers, 3) + mockRegistry.MockEvent(®istry.ServiceEvent{Action: remoting.EventTypeAdd, Service: providerUrl}) + time.Sleep(1e9) + assert.Len(t, registryDirectory.cacheInvokers, 4) + mockRegistry.MockEvents([]*registry.ServiceEvent{®istry.ServiceEvent{Action: remoting.EventTypeUpdate, Service: providerUrl}}) + time.Sleep(1e9) + assert.Len(t, registryDirectory.cacheInvokers, 1) + mockRegistry.MockEvents([]*registry.ServiceEvent{®istry.ServiceEvent{Action: remoting.EventTypeUpdate, Service: providerUrl}, + ®istry.ServiceEvent{Action: remoting.EventTypeUpdate, Service: providerUrl2}}) + time.Sleep(1e9) + assert.Len(t, registryDirectory.cacheInvokers, 2) + // clear all address + mockRegistry.MockEvents([]*registry.ServiceEvent{}) + time.Sleep(1e9) + assert.Len(t, registryDirectory.cacheInvokers, 0) +} + func normalRegistryDir(noMockEvent ...bool) (*RegistryDirectory, *registry.MockRegistry) { extension.SetProtocol(protocolwrapper.FILTER, protocolwrapper.NewMockProtocolFilter) @@ -202,17 +230,17 @@ func normalRegistryDir(noMockEvent ...bool) (*RegistryDirectory, *registry.MockR common.WithParamsValue(constant.GROUP_KEY, "group"), common.WithParamsValue(constant.VERSION_KEY, "1.0.0"), ) - url.SubURL = &suburl + url.SubURL = suburl mockRegistry, _ := registry.NewMockRegistry(&common.URL{}) - dir, _ := NewRegistryDirectory(&url, mockRegistry) + dir, _ := NewRegistryDirectory(url, mockRegistry) - go dir.(*RegistryDirectory).subscribe(&suburl) + go dir.(*RegistryDirectory).subscribe(suburl) if len(noMockEvent) == 0 { for i := 0; i < 3; i++ { mockRegistry.(*registry.MockRegistry).MockEvent( ®istry.ServiceEvent{ Action: remoting.EventTypeAdd, - Service: *common.NewURLWithOptions( + Service: common.NewURLWithOptions( common.WithPath("TEST"+strconv.FormatInt(int64(i), 10)), common.WithProtocol("dubbo"), ), diff --git a/registry/etcdv3/listener.go b/registry/etcdv3/listener.go index 436b6eca5b0dfb8514cbc21a47032490f7b1c21f..4bc387cafaa3c64539efb29a181a2198fbe8a30f 100644 --- a/registry/etcdv3/listener.go +++ b/registry/etcdv3/listener.go @@ -64,7 +64,7 @@ func (l *dataListener) DataChange(eventType remoting.Event) bool { } for _, v := range l.interestedURL { - if serviceURL.URLEqual(*v) { + if serviceURL.URLEqual(v) { l.listener.Process( &config_center.ConfigChangeEvent{ Key: eventType.Path, @@ -113,7 +113,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { } continue } - return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(common.URL)}, nil + return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(*common.URL)}, nil } } } diff --git a/registry/etcdv3/listener_test.go b/registry/etcdv3/listener_test.go index cc4ea257ccabb5591689ec961ae279e12f24dd29..ff7f63f614c6c43564dc10e412a833449b3bea8a 100644 --- a/registry/etcdv3/listener_test.go +++ b/registry/etcdv3/listener_test.go @@ -63,7 +63,6 @@ func (suite *RegistryTestSuite) SetupSuite() { } suite.etcd = e - return } // stop etcd server @@ -81,7 +80,7 @@ func (suite *RegistryTestSuite) TestDataChange() { 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") - listener.AddInterestedURL(&url) + listener.AddInterestedURL(url) if !listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"}) { t.Fatal("data change not ok") } diff --git a/registry/etcdv3/registry.go b/registry/etcdv3/registry.go index 2fec8eaad25e716fc5ed5ee33775d8898cb212e2..f3cc379bd8e94b15b678f0ac1d5ed5b6c917da6a 100644 --- a/registry/etcdv3/registry.go +++ b/registry/etcdv3/registry.go @@ -91,7 +91,7 @@ func newETCDV3Registry(url *common.URL) (registry.Registry, error) { r, etcdv3.WithName(etcdv3.RegistryETCDV3Client), etcdv3.WithTimeout(timeout), - etcdv3.WithEndpoints(url.Location), + etcdv3.WithEndpoints(strings.Split(url.Location, ",")...), ); err != nil { return nil, err } @@ -112,8 +112,9 @@ func (r *etcdV3Registry) InitListeners() { } // DoRegister actually do the register job in the registry center of etcd +// for lease func (r *etcdV3Registry) DoRegister(root string, node string) error { - return r.client.Create(path.Join(root, node), "") + return r.client.RegisterTemp(path.Join(root, node), "") } // nolint diff --git a/registry/etcdv3/registry_test.go b/registry/etcdv3/registry_test.go index 164fe9ca61793614e3d9d2f77f49cdfaebdd7317..d94eff656dbc4f46e4b34c8c50b20396b335c073 100644 --- a/registry/etcdv3/registry_test.go +++ b/registry/etcdv3/registry_test.go @@ -39,7 +39,7 @@ func initRegistry(t *testing.T) *etcdV3Registry { t.Fatal(err) } - reg, err := newETCDV3Registry(®url) + reg, err := newETCDV3Registry(regurl) if err != nil { t.Fatal(err) } @@ -86,7 +86,7 @@ func (suite *RegistryTestSuite) TestSubscribe() { err = reg2.Register(url) assert.NoError(t, err) - listener, err := reg2.DoSubscribe(&url) + listener, err := reg2.DoSubscribe(url) if err != nil { t.Fatal(err) } @@ -104,7 +104,7 @@ func (suite *RegistryTestSuite) TestConsumerDestroy() { url, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) reg := initRegistry(t) - _, err := reg.DoSubscribe(&url) + _, err := reg.DoSubscribe(url) if err != nil { t.Fatal(err) } diff --git a/registry/etcdv3/service_discovery.go b/registry/etcdv3/service_discovery.go index dceaa99df8061c6f46baa52eb6f5cebe4477f120..e8d4aea9a42634896c3c30e5c6b527a935179873 100644 --- a/registry/etcdv3/service_discovery.go +++ b/registry/etcdv3/service_discovery.go @@ -19,6 +19,7 @@ package etcdv3 import ( "fmt" + "strings" "sync" "time" ) @@ -313,7 +314,7 @@ func newEtcdV3ServiceDiscovery(name string) (registry.ServiceDiscovery, error) { client := etcdv3.NewServiceDiscoveryClient( etcdv3.WithName(etcdv3.RegistryETCDV3Client), etcdv3.WithTimeout(timeout), - etcdv3.WithEndpoints(remoteConfig.Address), + etcdv3.WithEndpoints(strings.Split(remoteConfig.Address, ",")...), ) descriptor := fmt.Sprintf("etcd-service-discovery[%s]", remoteConfig.Address) diff --git a/registry/event.go b/registry/event.go index 39fb00c740ef2e70e2cd6768fa4a4bb3226f832c..2ebc375597aef67b7605073b852ce53f68cb58e4 100644 --- a/registry/event.go +++ b/registry/event.go @@ -40,12 +40,37 @@ func init() { // ServiceEvent includes create, update, delete event type ServiceEvent struct { Action remoting.EventType - Service common.URL + Service *common.URL + // store the key for Service.Key() + key string + // If the url is updated, such as Merged. + updated bool } // String return the description of event -func (e ServiceEvent) String() string { - return fmt.Sprintf("ServiceEvent{Action{%s}, Path{%s}}", e.Action, e.Service) +func (e *ServiceEvent) String() string { + return fmt.Sprintf("ServiceEvent{Action{%s}, Path{%s}, Key{%s}}", e.Action, e.Service, e.key) +} + +// Update() update the url with the merged URL. Work with Updated() can reduce the process of some merging URL. +func (e *ServiceEvent) Update(url *common.URL) { + e.Service = url + e.updated = true +} + +// Updated() check if the url is updated. +// If the serviceEvent is updated, then it don't need merge url again. +func (e *ServiceEvent) Updated() bool { + return e.updated +} + +// Key() generate the key for service.Key(). It is cached once. +func (e *ServiceEvent) Key() string { + if len(e.key) > 0 { + return e.key + } + e.key = e.Service.Key() + return e.key } // ServiceInstancesChangedEvent represents service instances make some changing diff --git a/registry/event/metadata_service_url_params_customizer_test.go b/registry/event/metadata_service_url_params_customizer_test.go index 98ae2df883f590f4c3e4b379bb5a0fcbe46d946c..c041232b7836986db034da9c61acabc64050757e 100644 --- a/registry/event/metadata_service_url_params_customizer_test.go +++ b/registry/event/metadata_service_url_params_customizer_test.go @@ -70,27 +70,27 @@ func (m *mockMetadataService) ServiceName() (string, error) { panic("implement me") } -func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) { +func (m *mockMetadataService) ExportURL(*common.URL) (bool, error) { panic("implement me") } -func (m *mockMetadataService) UnexportURL(url common.URL) error { +func (m *mockMetadataService) UnexportURL(*common.URL) error { panic("implement me") } -func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) { +func (m *mockMetadataService) SubscribeURL(*common.URL) (bool, error) { panic("implement me") } -func (m *mockMetadataService) UnsubscribeURL(url common.URL) error { +func (m *mockMetadataService) UnsubscribeURL(*common.URL) error { panic("implement me") } -func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error { +func (m *mockMetadataService) PublishServiceDefinition(*common.URL) error { panic("implement me") } -func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { +func (m *mockMetadataService) GetExportedURLs(string, string, string, string) ([]interface{}, error) { return m.urls, nil } @@ -98,8 +98,8 @@ func (m *mockMetadataService) MethodMapper() map[string]string { panic("implement me") } -func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { - res := make([]common.URL, 0, len(m.urls)) +func (m *mockMetadataService) GetSubscribedURLs() ([]*common.URL, error) { + var res []*common.URL for _, ui := range m.urls { u, _ := common.NewURL(ui.(string)) res = append(res, u) @@ -107,15 +107,15 @@ func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { return res, nil } -func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { +func (m *mockMetadataService) GetServiceDefinition(string, string, string) (string, error) { panic("implement me") } -func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { +func (m *mockMetadataService) GetServiceDefinitionByServiceKey(string) (string, error) { panic("implement me") } -func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { +func (m *mockMetadataService) RefreshMetadata(string, string) (bool, error) { panic("implement me") } diff --git a/registry/event/service_revision_customizer.go b/registry/event/service_revision_customizer.go index fd21e8f4c7a71cedfe1de7e9c836e7cee278182e..4793e91948fe4c30fffbfd21f0dcc3efe57c5095 100644 --- a/registry/event/service_revision_customizer.go +++ b/registry/event/service_revision_customizer.go @@ -126,7 +126,7 @@ func resolveRevision(urls []interface{}) string { // append url params if we need it } - sort.Sort(sort.StringSlice(candidates)) + sort.Strings(candidates) // it's nearly impossible to be overflow res := uint64(0) diff --git a/registry/file/listener.go b/registry/file/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..3fe7400226067f1232ed7993b1fe1b5575b870df --- /dev/null +++ b/registry/file/listener.go @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package file + +import "github.com/apache/dubbo-go/config_center" + +// RegistryConfigurationListener represent the processor of flie watcher +type RegistryConfigurationListener struct { +} + +// Process submit the ConfigChangeEvent to the event chan to notify all observer +func (l *RegistryConfigurationListener) Process(configType *config_center.ConfigChangeEvent) { + +} diff --git a/registry/file/service_discovery.go b/registry/file/service_discovery.go new file mode 100644 index 0000000000000000000000000000000000000000..254c12688f47282343e0004dac86844ba51a3eb2 --- /dev/null +++ b/registry/file/service_discovery.go @@ -0,0 +1,285 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package file + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "strconv" +) + +import ( + gxset "github.com/dubbogo/gost/container/set" + gxpage "github.com/dubbogo/gost/page" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/config_center" + "github.com/apache/dubbo-go/config_center/file" + "github.com/apache/dubbo-go/registry" +) + +// init will put the service discovery into extension +func init() { + extension.SetServiceDiscovery(constant.FILE_KEY, newFileSystemServiceDiscovery) +} + +// fileServiceDiscovery is the implementation of service discovery based on file. +type fileSystemServiceDiscovery struct { + dynamicConfiguration file.FileSystemDynamicConfiguration + rootPath string + fileMap map[string]string +} + +func newFileSystemServiceDiscovery(name string) (registry.ServiceDiscovery, error) { + sdc, ok := config.GetBaseConfig().GetServiceDiscoveries(name) + if !ok || sdc.Protocol != constant.FILE_KEY { + return nil, perrors.New("could not init the instance because the config is invalid") + } + + rp, err := file.Home() + if err != nil { + return nil, perrors.WithStack(err) + } + + fdcf := extension.GetConfigCenterFactory(constant.FILE_KEY) + p := path.Join(rp, ".dubbo", constant.REGISTRY_KEY) + url, _ := common.NewURL("") + url.AddParamAvoidNil(file.CONFIG_CENTER_DIR_PARAM_NAME, p) + c, err := fdcf.GetDynamicConfiguration(url) + if err != nil { + return nil, perrors.WithStack(err) + } + + sd := &fileSystemServiceDiscovery{ + dynamicConfiguration: *c.(*file.FileSystemDynamicConfiguration), + rootPath: p, + fileMap: make(map[string]string), + } + + extension.AddCustomShutdownCallback(func() { + sd.Destroy() + }) + + for _, v := range sd.GetServices().Values() { + for _, i := range sd.GetInstances(v.(string)) { + // like java do nothing + l := &RegistryConfigurationListener{} + sd.dynamicConfiguration.AddListener(getServiceInstanceId(i), l, config_center.WithGroup(getServiceName(i))) + } + } + + return sd, nil +} + +// nolint +func (fssd *fileSystemServiceDiscovery) String() string { + return fmt.Sprintf("file-system-service-discovery") +} + +// Destroy will destroy the service discovery. +// If the discovery cannot be destroy, it will return an error. +func (fssd *fileSystemServiceDiscovery) Destroy() error { + fssd.dynamicConfiguration.Close() + + for _, f := range fssd.fileMap { + fssd.releaseAndRemoveRegistrationFiles(f) + } + + return nil +} + +// nolint +func (fssd *fileSystemServiceDiscovery) releaseAndRemoveRegistrationFiles(file string) { + os.RemoveAll(file) +} + +// ----------------- registration ---------------- + +// Register will register an instance of ServiceInstance to registry +func (fssd *fileSystemServiceDiscovery) Register(instance registry.ServiceInstance) error { + id := getServiceInstanceId(instance) + sn := getServiceName(instance) + + c, err := toJsonString(instance) + if err != nil { + return perrors.WithStack(err) + } + + err = fssd.dynamicConfiguration.PublishConfig(id, sn, c) + if err != nil { + return perrors.WithStack(err) + } + + fssd.fileMap[id] = fssd.dynamicConfiguration.GetPath(id, sn) + + return nil +} + +// nolint +func getServiceInstanceId(si registry.ServiceInstance) string { + if si.GetId() == "" { + return si.GetHost() + "." + strconv.Itoa(si.GetPort()) + } + + return si.GetId() +} + +// nolint +func getServiceName(si registry.ServiceInstance) string { + return si.GetServiceName() +} + +// toJsonString to json string +func toJsonString(si registry.ServiceInstance) (string, error) { + bytes, err := json.Marshal(si) + if err != nil { + return "", perrors.WithStack(err) + } + + return string(bytes), nil +} + +// Update will update the data of the instance in registry +func (fssd *fileSystemServiceDiscovery) Update(instance registry.ServiceInstance) error { + return fssd.Register(instance) +} + +// Unregister will unregister this instance from registry +func (fssd *fileSystemServiceDiscovery) Unregister(instance registry.ServiceInstance) error { + id := getServiceInstanceId(instance) + sn := getServiceName(instance) + + err := fssd.dynamicConfiguration.RemoveConfig(id, sn) + if err != nil { + return perrors.WithStack(err) + } + + delete(fssd.fileMap, instance.GetId()) + return nil +} + +// ----------------- discovery ------------------- +// GetDefaultPageSize will return the default page size +func (fssd *fileSystemServiceDiscovery) GetDefaultPageSize() int { + return 100 +} + +// GetServices will return the all service names. +func (fssd *fileSystemServiceDiscovery) GetServices() *gxset.HashSet { + r := gxset.NewSet() + // dynamicConfiguration root path is the actual root path + fileInfo, _ := ioutil.ReadDir(fssd.dynamicConfiguration.RootPath()) + + for _, file := range fileInfo { + if file.IsDir() { + r.Add(file.Name()) + } + } + + return r +} + +// GetInstances will return all service instances with serviceName +func (fssd *fileSystemServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { + set, err := fssd.dynamicConfiguration.GetConfigKeysByGroup(serviceName) + if err != nil { + logger.Errorf("[FileServiceDiscovery] Could not query the instances for service{%s}, error = err{%v} ", + serviceName, err) + return make([]registry.ServiceInstance, 0, 0) + } + + res := make([]registry.ServiceInstance, 0, set.Size()) + for _, v := range set.Values() { + id := v.(string) + p, err := fssd.dynamicConfiguration.GetProperties(id, config_center.WithGroup(serviceName)) + if err != nil { + logger.Errorf("[FileServiceDiscovery] Could not get the properties for id{%s}, service{%s}, "+ + "error = err{%v} ", + id, serviceName, err) + return make([]registry.ServiceInstance, 0, 0) + } + + dsi := ®istry.DefaultServiceInstance{} + err = json.Unmarshal([]byte(p), dsi) + if err != nil { + logger.Errorf("[FileServiceDiscovery] Could not unmarshal the properties for id{%s}, service{%s}, "+ + "error = err{%v} ", + id, serviceName, err) + return make([]registry.ServiceInstance, 0, 0) + } + + res = append(res, dsi) + } + + return res +} + +// GetInstancesByPage will return a page containing instances of ServiceInstance with the serviceName +// the page will start at offset +func (fssd *fileSystemServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { + return nil +} + +// GetHealthyInstancesByPage will return a page containing instances of ServiceInstance. +// The param healthy indices that the instance should be healthy or not. +// The page will start at offset +func (fssd *fileSystemServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, + healthy bool) gxpage.Pager { + return nil +} + +// Batch get all instances by the specified service names +func (fssd *fileSystemServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, + requestedSize int) map[string]gxpage.Pager { + return nil +} + +// ----------------- event ---------------------- +// AddListener adds a new ServiceInstancesChangedListener +// client +func (fssd *fileSystemServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { + //fssd.dynamicConfiguration.AddListener(listener.ServiceName) + return nil +} + +// DispatchEventByServiceName dispatches the ServiceInstancesChangedEvent to service instance whose name is serviceName +func (fssd *fileSystemServiceDiscovery) DispatchEventByServiceName(serviceName string) error { + return fssd.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, fssd.GetInstances(serviceName))) +} + +// DispatchEventForInstances dispatches the ServiceInstancesChangedEvent to target instances +func (fssd *fileSystemServiceDiscovery) DispatchEventForInstances(serviceName string, + instances []registry.ServiceInstance) error { + return fssd.DispatchEvent(registry.NewServiceInstancesChangedEvent(serviceName, instances)) +} + +// DispatchEvent dispatches the event +func (fssd *fileSystemServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { + extension.GetGlobalDispatcher().Dispatch(event) + return nil +} diff --git a/registry/file/service_discovery_test.go b/registry/file/service_discovery_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0bffcae31d039a49d8cf696a6de2f6858c42ada2 --- /dev/null +++ b/registry/file/service_discovery_test.go @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package file + +import ( + "math/rand" + "strconv" + "testing" + "time" +) + +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/config" + "github.com/apache/dubbo-go/registry" +) + +var ( + testName = "test" +) + +func TestNewFileSystemServiceDiscoveryAndDestroy(t *testing.T) { + prepareData() + serviceDiscovery, err := newFileSystemServiceDiscovery(testName) + assert.NoError(t, err) + assert.NotNil(t, serviceDiscovery) + defer serviceDiscovery.Destroy() +} + +func TestCURDFileSystemServiceDiscovery(t *testing.T) { + prepareData() + serviceDiscovery, err := extension.GetServiceDiscovery(constant.FILE_KEY, testName) + assert.NoError(t, err) + md := make(map[string]string) + + rand.Seed(time.Now().Unix()) + serviceName := "service-name" + strconv.Itoa(rand.Intn(10000)) + md["t1"] = "test1" + r1 := ®istry.DefaultServiceInstance{ + Id: "123456789", + ServiceName: serviceName, + Host: "127.0.0.1", + Port: 2233, + Enable: true, + Healthy: true, + Metadata: md, + } + err = serviceDiscovery.Register(r1) + assert.NoError(t, err) + + instances := serviceDiscovery.GetInstances(r1.ServiceName) + assert.Equal(t, 1, len(instances)) + assert.Equal(t, r1.Id, instances[0].GetId()) + assert.Equal(t, r1.ServiceName, instances[0].GetServiceName()) + assert.Equal(t, r1.Port, instances[0].GetPort()) + + err = serviceDiscovery.Unregister(r1) + assert.NoError(t, err) + + err = serviceDiscovery.Register(r1) + + defer serviceDiscovery.Destroy() +} + +func prepareData() { + config.GetBaseConfig().ServiceDiscoveries[testName] = &config.ServiceDiscoveryConfig{ + Protocol: "file", + } +} diff --git a/registry/kubernetes/listener.go b/registry/kubernetes/listener.go index 24c8d81614c7dd323e4f23ec7de5d28b24eecb70..e20b7c7e7d2f72349f7ec268bbb3b09f135dc6ed 100644 --- a/registry/kubernetes/listener.go +++ b/registry/kubernetes/listener.go @@ -65,7 +65,7 @@ func (l *dataListener) DataChange(eventType remoting.Event) bool { } for _, v := range l.interestedURL { - if serviceURL.URLEqual(*v) { + if serviceURL.URLEqual(v) { l.listener.Process( &config_center.ConfigChangeEvent{ Key: eventType.Path, @@ -114,7 +114,7 @@ func (l *configurationListener) Next() (*registry.ServiceEvent, error) { } continue } - return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(common.URL)}, nil + return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(*common.URL)}, nil } } } diff --git a/registry/kubernetes/listener_test.go b/registry/kubernetes/listener_test.go index ccaaf80907f9eb3d4956758374f518c66fa613d5..f1d8ff41761a841aa2bec888336756854ff16874 100644 --- a/registry/kubernetes/listener_test.go +++ b/registry/kubernetes/listener_test.go @@ -166,7 +166,7 @@ var clientPodJsonData = `{ func Test_DataChange(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") - listener.AddInterestedURL(&url) + listener.AddInterestedURL(url) int := listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"}) assert.Equal(t, true, int) } @@ -179,7 +179,7 @@ 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") - listener.AddInterestedURL(&url) + listener.AddInterestedURL(url) if !listener.DataChange(remoting.Event{Path: "/dubbo/com.ikurento.user.UserProvider/providers/jsonrpc%3A%2F%2F127.0.0.1%3A20001%2Fcom.ikurento.user.UserProvider%3Fanyhost%3Dtrue%26app.version%3D0.0.1%26application%3DBDTService%26category%3Dproviders%26cluster%3Dfailover%26dubbo%3Ddubbo-provider-golang-2.6.0%26environment%3Ddev%26group%3D%26interface%3Dcom.ikurento.user.UserProvider%26ip%3D10.32.20.124%26loadbalance%3Drandom%26methods.GetUser.loadbalance%3Drandom%26methods.GetUser.retries%3D1%26methods.GetUser.weight%3D0%26module%3Ddubbogo%2Buser-info%2Bserver%26name%3DBDTService%26organization%3Dikurento.com%26owner%3DZX%26pid%3D74500%26retries%3D0%26service.filter%3Decho%26side%3Dprovider%26timestamp%3D1560155407%26version%3D%26warmup%3D100"}) { t.Fatal("data change not ok") } diff --git a/registry/kubernetes/registry.go b/registry/kubernetes/registry.go index 88895855689df42f2cd66ec158e6b8f2806ef6f0..c1e559e48dcc64972c9405e15405d7e6febcc845 100644 --- a/registry/kubernetes/registry.go +++ b/registry/kubernetes/registry.go @@ -27,7 +27,6 @@ import ( import ( "github.com/apache/dubbo-getty" - "github.com/dubbogo/gost/net" perrors "github.com/pkg/errors" v1 "k8s.io/api/core/v1" ) @@ -54,7 +53,7 @@ const ( func init() { processID = fmt.Sprintf("%d", os.Getpid()) - localIP, _ = gxnet.GetLocalIP() + localIP = common.GetLocalIp() extension.SetRegistry(Name, newKubernetesRegistry) } diff --git a/registry/kubernetes/registry_test.go b/registry/kubernetes/registry_test.go index 347dadcd2c462e3a1caf9829b051a665ec61e8e3..9fb409a222be914cad5b64e7ff3aab9bc97a6f33 100644 --- a/registry/kubernetes/registry_test.go +++ b/registry/kubernetes/registry_test.go @@ -231,7 +231,7 @@ func getTestRegistry(t *testing.T) *kubernetesRegistry { if err != nil { t.Fatal(err) } - out, err := newMockKubernetesRegistry(®url, pl) + out, err := newMockKubernetesRegistry(regurl, pl) if err != nil { t.Fatal(err) } @@ -268,7 +268,7 @@ func TestSubscribe(t *testing.T) { t.Fatal(err) } - listener, err := r.DoSubscribe(&url) + listener, err := r.DoSubscribe(url) if err != nil { t.Fatal(err) } @@ -301,7 +301,7 @@ func TestConsumerDestroy(t *testing.T) { common.WithParamsValue(constant.CLUSTER_KEY, "mock"), common.WithMethods([]string{"GetUser", "AddUser"})) - _, err := r.DoSubscribe(&url) + _, err := r.DoSubscribe(url) if err != nil { t.Fatal(err) } @@ -336,7 +336,7 @@ func TestNewRegistry(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = newKubernetesRegistry(®Url) + _, err = newKubernetesRegistry(regUrl) if err == nil { t.Fatal("not in cluster, should be a err") } diff --git a/registry/mock_registry.go b/registry/mock_registry.go index 10561d0f49e995c94c93fa0463fc0b0421ff6e20..7c269c3a4044079d19c7c41b53a9ae04b9eab86b 100644 --- a/registry/mock_registry.go +++ b/registry/mock_registry.go @@ -18,6 +18,7 @@ package registry import ( + "fmt" "time" ) @@ -32,14 +33,16 @@ import ( // MockRegistry is used as mock registry type MockRegistry struct { - listener *listener - destroyed *atomic.Bool + listener *listener + destroyed *atomic.Bool + allAddress chan []*ServiceEvent } // NewMockRegistry creates a mock registry func NewMockRegistry(url *common.URL) (Registry, error) { registry := &MockRegistry{ - destroyed: atomic.NewBool(false), + destroyed: atomic.NewBool(false), + allAddress: make(chan []*ServiceEvent), } listener := &listener{count: 0, registry: registry, listenChan: make(chan *ServiceEvent)} registry.listener = listener @@ -47,12 +50,12 @@ func NewMockRegistry(url *common.URL) (Registry, error) { } // Register is used as a mock registry -func (*MockRegistry) Register(url common.URL) error { +func (*MockRegistry) Register(url *common.URL) error { return nil } // nolint -func (r *MockRegistry) UnRegister(conf common.URL) error { +func (r *MockRegistry) UnRegister(conf *common.URL) error { return nil } @@ -68,8 +71,8 @@ func (r *MockRegistry) IsAvailable() bool { } // nolint -func (r *MockRegistry) GetUrl() common.URL { - return common.URL{} +func (r *MockRegistry) GetUrl() *common.URL { + return nil } func (r *MockRegistry) subscribe(*common.URL) (Listener, error) { @@ -80,22 +83,12 @@ func (r *MockRegistry) subscribe(*common.URL) (Listener, error) { func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) error { go func() { for { - if !r.IsAvailable() { - logger.Warnf("event listener game over.") - time.Sleep(time.Duration(3) * time.Second) - return - } - - listener, err := r.subscribe(url) - if err != nil { - if !r.IsAvailable() { - logger.Warnf("event listener game over.") - return - } - time.Sleep(time.Duration(3) * time.Second) + t, listener := r.checkLoopSubscribe(url) + if t == 0 { continue + } else if t == -1 { + return } - for { serviceEvent, err := listener.Next() if err != nil { @@ -109,6 +102,26 @@ func (r *MockRegistry) Subscribe(url *common.URL, notifyListener NotifyListener) } } }() + go func() { + for { + t, _ := r.checkLoopSubscribe(url) + if t == 0 { + continue + } else if t == -1 { + return + } + + for { + select { + case e := <-r.allAddress: + notifyListener.NotifyAll(e, func() { + fmt.Print("notify all ok") + }) + break + } + } + } + }() return nil } @@ -138,3 +151,27 @@ func (*listener) Close() { func (r *MockRegistry) MockEvent(event *ServiceEvent) { r.listener.listenChan <- event } + +// nolint +func (r *MockRegistry) MockEvents(events []*ServiceEvent) { + r.allAddress <- events +} + +func (r *MockRegistry) checkLoopSubscribe(url *common.URL) (int, Listener) { + if !r.IsAvailable() { + logger.Warnf("event listener game over.") + time.Sleep(time.Duration(3) * time.Second) + return -1, nil + } + + listener, err := r.subscribe(url) + if err != nil { + if !r.IsAvailable() { + logger.Warnf("event listener game over.") + return -1, nil + } + time.Sleep(time.Duration(3) * time.Second) + return 0, nil + } + return 1, listener +} diff --git a/registry/nacos/listener.go b/registry/nacos/listener.go index cf6a73d38fbefc1ecb6a80e4911bb0d8fb13a6f6..7f27326d6d7b1000688cb03cd6406ea53745a119 100644 --- a/registry/nacos/listener.go +++ b/registry/nacos/listener.go @@ -43,7 +43,7 @@ import ( type nacosListener struct { namingClient naming_client.INamingClient - listenUrl common.URL + listenUrl *common.URL events chan *config_center.ConfigChangeEvent instanceMap map[string]model.Instance cacheLock sync.Mutex @@ -52,7 +52,7 @@ type nacosListener struct { } // NewRegistryDataListener creates a data listener for nacos -func NewNacosListener(url common.URL, namingClient naming_client.INamingClient) (*nacosListener, error) { +func NewNacosListener(url *common.URL, namingClient naming_client.INamingClient) (*nacosListener, error) { listener := &nacosListener{ namingClient: namingClient, listenUrl: url, events: make(chan *config_center.ConfigChangeEvent, 32), @@ -154,25 +154,25 @@ func (nl *nacosListener) Callback(services []model.SubscribeService, err error) for i := range addInstances { newUrl := generateUrl(addInstances[i]) if newUrl != nil { - nl.process(&config_center.ConfigChangeEvent{Value: *newUrl, ConfigType: remoting.EventTypeAdd}) + nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeAdd}) } } for i := range delInstances { newUrl := generateUrl(delInstances[i]) if newUrl != nil { - nl.process(&config_center.ConfigChangeEvent{Value: *newUrl, ConfigType: remoting.EventTypeDel}) + nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeDel}) } } for i := range updateInstances { newUrl := generateUrl(updateInstances[i]) if newUrl != nil { - nl.process(&config_center.ConfigChangeEvent{Value: *newUrl, ConfigType: remoting.EventTypeUpdate}) + nl.process(&config_center.ConfigChangeEvent{Value: newUrl, ConfigType: remoting.EventTypeUpdate}) } } } -func getSubscribeName(url common.URL) string { +func getSubscribeName(url *common.URL) string { var buffer bytes.Buffer buffer.Write([]byte(common.DubboNodes[common.PROVIDER])) @@ -210,7 +210,7 @@ func (nl *nacosListener) Next() (*registry.ServiceEvent, error) { case e := <-nl.events: logger.Debugf("got nacos event %s", e) - return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(common.URL)}, nil + return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(*common.URL)}, nil } } } diff --git a/registry/nacos/registry.go b/registry/nacos/registry.go index 411090820c7682ab9c3b5576ea8ad5207c2c899f..ae2345e6d5c90fcfc5c4dfb5b4da07bcf1b6f43e 100644 --- a/registry/nacos/registry.go +++ b/registry/nacos/registry.go @@ -26,7 +26,6 @@ import ( ) import ( - gxnet "github.com/dubbogo/gost/net" "github.com/nacos-group/nacos-sdk-go/clients" "github.com/nacos-group/nacos-sdk-go/clients/naming_client" nacosConstant "github.com/nacos-group/nacos-sdk-go/common/constant" @@ -52,23 +51,23 @@ const ( ) func init() { - localIP, _ = gxnet.GetLocalIP() + localIP = common.GetLocalIp() extension.SetRegistry(constant.NACOS_KEY, newNacosRegistry) } type nacosRegistry struct { *common.URL namingClient naming_client.INamingClient - registryUrls []common.URL + registryUrls []*common.URL } -func getCategory(url common.URL) string { +func getCategory(url *common.URL) string { role, _ := strconv.Atoi(url.GetParam(constant.ROLE_KEY, strconv.Itoa(constant.NACOS_DEFAULT_ROLETYPE))) category := common.DubboNodes[role] return category } -func getServiceName(url common.URL) string { +func getServiceName(url *common.URL) string { var buffer bytes.Buffer buffer.Write([]byte(getCategory(url))) @@ -78,7 +77,7 @@ func getServiceName(url common.URL) string { return buffer.String() } -func appendParam(target *bytes.Buffer, url common.URL, key string) { +func appendParam(target *bytes.Buffer, url *common.URL, key string) { value := url.GetParam(key, "") if strings.TrimSpace(value) != "" { target.Write([]byte(constant.NACOS_SERVICE_NAME_SEPARATOR)) @@ -86,7 +85,7 @@ func appendParam(target *bytes.Buffer, url common.URL, key string) { } } -func createRegisterParam(url common.URL, serviceName string) vo.RegisterInstanceParam { +func createRegisterParam(url *common.URL, serviceName string) vo.RegisterInstanceParam { category := getCategory(url) params := make(map[string]string) @@ -119,7 +118,7 @@ func createRegisterParam(url common.URL, serviceName string) vo.RegisterInstance } // Register will register the service @url to its nacos registry center -func (nr *nacosRegistry) Register(url common.URL) error { +func (nr *nacosRegistry) Register(url *common.URL) error { serviceName := getServiceName(url) param := createRegisterParam(url, serviceName) isRegistry, err := nr.namingClient.RegisterInstance(param) @@ -133,7 +132,7 @@ func (nr *nacosRegistry) Register(url common.URL) error { return nil } -func createDeregisterParam(url common.URL, serviceName string) vo.DeregisterInstanceParam { +func createDeregisterParam(url *common.URL, serviceName string) vo.DeregisterInstanceParam { if len(url.Ip) == 0 { url.Ip = localIP } @@ -149,7 +148,7 @@ func createDeregisterParam(url common.URL, serviceName string) vo.DeregisterInst } } -func (nr *nacosRegistry) DeRegister(url common.URL) error { +func (nr *nacosRegistry) DeRegister(url *common.URL) error { serviceName := getServiceName(url) param := createDeregisterParam(url, serviceName) isDeRegistry, err := nr.namingClient.DeregisterInstance(param) @@ -163,12 +162,12 @@ func (nr *nacosRegistry) DeRegister(url common.URL) error { } // UnRegister -func (nr *nacosRegistry) UnRegister(conf common.URL) error { +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) + return NewNacosListener(conf, nr.namingClient) } // subscribe from registry @@ -203,7 +202,6 @@ func (nr *nacosRegistry) Subscribe(url *common.URL, notifyListener registry.Noti } } - return nil } // UnSubscribe : @@ -212,8 +210,8 @@ func (nr *nacosRegistry) UnSubscribe(url *common.URL, notifyListener registry.No } // GetUrl gets its registration URL -func (nr *nacosRegistry) GetUrl() common.URL { - return *nr.URL +func (nr *nacosRegistry) GetUrl() *common.URL { + return nr.URL } // IsAvailable determines nacos registry center whether it is available @@ -244,12 +242,12 @@ func newNacosRegistry(url *common.URL) (registry.Registry, error) { if err != nil { return &nacosRegistry{}, err } - registry := &nacosRegistry{ + tmpRegistry := &nacosRegistry{ URL: url, namingClient: client, - registryUrls: []common.URL{}, + registryUrls: []*common.URL{}, } - return registry, nil + return tmpRegistry, nil } // getNacosConfig will return the nacos config @@ -288,6 +286,7 @@ func getNacosConfig(url *common.URL) (map[string]interface{}, error) { clientConfig.CacheDir = url.GetParam(constant.NACOS_CACHE_DIR_KEY, "") clientConfig.LogDir = url.GetParam(constant.NACOS_LOG_DIR_KEY, "") clientConfig.Endpoint = url.GetParam(constant.NACOS_ENDPOINT, "") + clientConfig.NamespaceId = url.GetParam(constant.NACOS_NAMESPACE_ID, "") clientConfig.NotLoadCacheAtStart = true configMap["clientConfig"] = clientConfig diff --git a/registry/nacos/registry_test.go b/registry/nacos/registry_test.go index d0311b200b27081c60bc97b2307a54774ca977bd..078b8ce59c29acae35ccbefafbb9cbfb3d0b205d 100644 --- a/registry/nacos/registry_test.go +++ b/registry/nacos/registry_test.go @@ -19,9 +19,11 @@ package nacos import ( "encoding/json" + "net/http" "net/url" "strconv" "testing" + "time" ) import ( @@ -35,6 +37,9 @@ import ( ) func TestNacosRegistry_Register(t *testing.T) { + if !checkNacosServerAlive() { + return + } regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) urlMap := url.Values{} urlMap.Set(constant.GROUP_KEY, "guangzhou-idc") @@ -44,7 +49,7 @@ func TestNacosRegistry_Register(t *testing.T) { urlMap.Set(constant.CLUSTER_KEY, "mock") 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) + reg, err := newNacosRegistry(regurl) assert.Nil(t, err) if err != nil { t.Errorf("new nacos registry error:%s \n", err.Error()) @@ -64,6 +69,9 @@ func TestNacosRegistry_Register(t *testing.T) { } func TestNacosRegistry_Subscribe(t *testing.T) { + if !checkNacosServerAlive() { + return + } regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) urlMap := url.Values{} urlMap.Set(constant.GROUP_KEY, "guangzhou-idc") @@ -74,7 +82,7 @@ func TestNacosRegistry_Subscribe(t *testing.T) { urlMap.Set(constant.NACOS_PATH_KEY, "") testUrl, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) - reg, _ := newNacosRegistry(®url) + reg, _ := newNacosRegistry(regurl) err := reg.Register(testUrl) assert.Nil(t, err) if err != nil { @@ -83,8 +91,8 @@ func TestNacosRegistry_Subscribe(t *testing.T) { } regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) - reg2, _ := newNacosRegistry(®url) - listener, err := reg2.(*nacosRegistry).subscribe(&testUrl) + reg2, _ := newNacosRegistry(regurl) + listener, err := reg2.(*nacosRegistry).subscribe(testUrl) assert.Nil(t, err) if err != nil { t.Errorf("subscribe error:%s \n", err.Error()) @@ -102,6 +110,9 @@ func TestNacosRegistry_Subscribe(t *testing.T) { } func TestNacosRegistry_Subscribe_del(t *testing.T) { + if !checkNacosServerAlive() { + return + } regurl, _ := common.NewURL("registry://console.nacos.io:80", common.WithParamsValue(constant.ROLE_KEY, strconv.Itoa(common.PROVIDER))) urlMap := url.Values{} urlMap.Set(constant.GROUP_KEY, "guangzhou-idc") @@ -113,7 +124,7 @@ func TestNacosRegistry_Subscribe_del(t *testing.T) { url1, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) url2, _ := common.NewURL("dubbo://127.0.0.2:20000/com.ikurento.user.UserProvider", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) - reg, _ := newNacosRegistry(®url) + reg, _ := newNacosRegistry(regurl) err := reg.Register(url1) assert.Nil(t, err) if err != nil { @@ -128,8 +139,8 @@ func TestNacosRegistry_Subscribe_del(t *testing.T) { } regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) - reg2, _ := newNacosRegistry(®url) - listener, err := reg2.(*nacosRegistry).subscribe(&url1) + reg2, _ := newNacosRegistry(regurl) + listener, err := reg2.(*nacosRegistry).subscribe(url1) assert.Nil(t, err) if err != nil { t.Errorf("subscribe error:%s \n", err.Error()) @@ -177,8 +188,8 @@ func TestNacosListener_Close(t *testing.T) { urlMap.Set(constant.CLUSTER_KEY, "mock") urlMap.Set(constant.NACOS_PATH_KEY, "") url1, _ := common.NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider2", common.WithParams(urlMap), common.WithMethods([]string{"GetUser", "AddUser"})) - reg, _ := newNacosRegistry(®url) - listener, err := reg.(*nacosRegistry).subscribe(&url1) + reg, _ := newNacosRegistry(regurl) + listener, err := reg.(*nacosRegistry).subscribe(url1) assert.Nil(t, err) if err != nil { t.Errorf("subscribe error:%s \n", err.Error()) @@ -188,3 +199,11 @@ func TestNacosListener_Close(t *testing.T) { _, err = listener.Next() assert.NotNil(t, err) } + +func checkNacosServerAlive() bool { + c := http.Client{Timeout: time.Second} + if _, err := c.Get("http://console.nacos.io/nacos/"); err != nil { + return false + } + return true +} diff --git a/registry/nacos/service_discovery.go b/registry/nacos/service_discovery.go index 0e5ad8e6990856aeb0dfdde72f9c3f7fdae3e985..b38e150e51075ce47ee38c9c8c9c7280e4437c53 100644 --- a/registry/nacos/service_discovery.go +++ b/registry/nacos/service_discovery.go @@ -141,7 +141,8 @@ func (n *nacosServiceDiscovery) GetInstances(serviceName string) []registry.Serv GroupName: n.group, }) if err != nil { - logger.Errorf("Could not query the instances for service: " + serviceName + ", group: " + n.group) + logger.Errorf("Could not query the instances for service: %+v, group: %+v . It happened err %+v", + serviceName, n.group, err) return make([]registry.ServiceInstance, 0, 0) } res := make([]registry.ServiceInstance, 0, len(instances)) diff --git a/registry/nacos/service_discovery_test.go b/registry/nacos/service_discovery_test.go index 119be0b3aad3a828470c8c72c775abaada9512c2..3b09136d2e273090bea339e0a15b25dde719b6ac 100644 --- a/registry/nacos/service_discovery_test.go +++ b/registry/nacos/service_discovery_test.go @@ -81,6 +81,9 @@ func TestNacosServiceDiscovery_Destroy(t *testing.T) { } func TestNacosServiceDiscovery_CRUD(t *testing.T) { + if !checkNacosServerAlive() { + return + } prepareData() extension.SetEventDispatcher("mock", func() observer.EventDispatcher { return &dispatcher.MockEventDispatcher{} diff --git a/registry/protocol/protocol.go b/registry/protocol/protocol.go index c0608ad7989e69c104f07aa95e9a77fb9ac212fa..3be88c3beb6d909c619ff5c8ff51b90359a31d7b 100644 --- a/registry/protocol/protocol.go +++ b/registry/protocol/protocol.go @@ -22,6 +22,7 @@ import ( "strings" "sync" ) + import ( gxset "github.com/dubbogo/gost/container/set" ) @@ -31,7 +32,6 @@ 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/proxy/proxy_factory" "github.com/apache/dubbo-go/config" "github.com/apache/dubbo-go/config_center" _ "github.com/apache/dubbo-go/config_center/configurator" @@ -54,9 +54,10 @@ var ( type registryProtocol struct { invokers []protocol.Invoker - // Registry Map<RegistryAddress, Registry> + // 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. + // 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 @@ -100,10 +101,9 @@ func getUrlToRegistry(providerUrl *common.URL, registryUrl *common.URL) *common. // 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() { + for k := range url.GetParams() { if strings.HasPrefix(k, ".") { removeSet.Add(k) } @@ -130,32 +130,30 @@ func (proto *registryProtocol) GetRegistries() []registry.Registry { } // Refer provider service from registry center -func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { +func (proto *registryProtocol) Refer(url *common.URL) protocol.Invoker { var registryUrl = url var serviceUrl = registryUrl.SubURL if registryUrl.Protocol == constant.REGISTRY_PROTOCOL { - protocol := registryUrl.GetParam(constant.REGISTRY_KEY, "") - registryUrl.Protocol = protocol + registryUrl.Protocol = registryUrl.GetParam(constant.REGISTRY_KEY, "") } var reg registry.Registry - if regI, loaded := proto.registries.Load(registryUrl.Key()); !loaded { - reg = getRegistry(®istryUrl) + reg = getRegistry(registryUrl) proto.registries.Store(registryUrl.Key(), reg) } else { reg = regI.(registry.Registry) } // new registry directory for store service url from registry - directory, err := extension.GetDefaultRegistryDirectory(®istryUrl, reg) + directory, err := extension.GetDefaultRegistryDirectory(registryUrl, reg) if err != nil { - logger.Errorf("consumer service %v create registry directory error, error message is %s, and will return nil invoker!", + logger.Errorf("consumer service %v create registry directory error, error message is %s, and will return nil invoker!", serviceUrl.String(), err.Error()) return nil } - err = reg.Register(*serviceUrl) + err = reg.Register(serviceUrl) if err != nil { logger.Errorf("consumer service %v register registry %v error, error message is %s", serviceUrl.String(), registryUrl.String(), err.Error()) @@ -163,7 +161,6 @@ func (proto *registryProtocol) Refer(url common.URL) protocol.Invoker { // new cluster invoker cluster := extension.GetCluster(serviceUrl.GetParam(constant.CLUSTER_KEY, constant.DEFAULT_CLUSTER)) - invoker := cluster.Join(directory) proto.invokers = append(proto.invokers, invoker) return invoker @@ -196,7 +193,7 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte } registeredProviderUrl := getUrlToRegistry(providerUrl, registryUrl) - err := reg.Register(*registeredProviderUrl) + 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()) @@ -204,7 +201,7 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte } key := getCacheKey(providerUrl) - logger.Infof("The cached exporter keys is %v !", key) + logger.Infof("The cached exporter keys is %v!", key) cachedExporter, loaded := proto.bounds.Load(key) if loaded { logger.Infof("The exporter has been cached, and will return cached exporter!") @@ -217,7 +214,6 @@ func (proto *registryProtocol) Export(invoker protocol.Invoker) protocol.Exporte go reg.Subscribe(overriderUrl, overrideSubscribeListener) return cachedExporter.(protocol.Exporter) - } func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common.URL) { @@ -229,7 +225,6 @@ func (proto *registryProtocol) reExport(invoker protocol.Invoker, newUrl *common proto.bounds.Delete(key) proto.Export(wrappedNewInvoker) // TODO: unregister & unsubscribe - } } @@ -246,12 +241,22 @@ func newOverrideSubscribeListener(overriderUrl *common.URL, invoker protocol.Inv // Notify will be triggered when a service change notification is received. func (nl *overrideSubscribeListener) Notify(event *registry.ServiceEvent) { - if isMatched(&(event.Service), nl.url) && event.Action == remoting.EventTypeAdd { - nl.configurator = extension.GetDefaultConfigurator(&(event.Service)) + if isMatched(event.Service, nl.url) && event.Action == remoting.EventTypeAdd { + nl.configurator = extension.GetDefaultConfigurator(event.Service) nl.doOverrideIfNecessary() } } +func (nl *overrideSubscribeListener) NotifyAll(events []*registry.ServiceEvent, callback func()) { + defer callback() + if len(events) == 0 { + return + } + for _, e := range events { + nl.Notify(e) + } +} + func (nl *overrideSubscribeListener) doOverrideIfNecessary() { providerUrl := getProviderUrl(nl.originInvoker) key := getCacheKey(providerUrl) @@ -274,9 +279,9 @@ func (nl *overrideSubscribeListener) doOverrideIfNecessary() { } if currentUrl.String() != providerUrl.String() { - newRegUrl := nl.originInvoker.GetUrl() - setProviderUrl(&newRegUrl, providerUrl) - nl.protocol.reExport(nl.originInvoker, &newRegUrl) + newRegUrl := nl.originInvoker.GetUrl().Clone() + setProviderUrl(newRegUrl, providerUrl) + nl.protocol.reExport(nl.originInvoker, newRegUrl) } } } @@ -366,12 +371,11 @@ func (proto *registryProtocol) Destroy() { func getRegistryUrl(invoker protocol.Invoker) *common.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 + url.Protocol = url.GetParam(constant.REGISTRY_KEY, "") } - return &url + return url } func getProviderUrl(invoker protocol.Invoker) *common.URL { @@ -400,14 +404,12 @@ type wrappedInvoker struct { func newWrappedInvoker(invoker protocol.Invoker, url *common.URL) *wrappedInvoker { return &wrappedInvoker{ invoker: invoker, - BaseInvoker: *protocol.NewBaseInvoker(*url), + BaseInvoker: *protocol.NewBaseInvoker(url), } } // Invoke remote service base on URL of wrappedInvoker func (ivk *wrappedInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result { - // get right url - ivk.invoker.(*proxy_factory.ProxyInvoker).BaseInvoker = *protocol.NewBaseInvoker(ivk.GetUrl()) return ivk.invoker.Invoke(ctx, invocation) } diff --git a/registry/protocol/protocol_test.go b/registry/protocol/protocol_test.go index 2d6e0248fddb88bfa5ce19546fb7aed703b0fd3c..0fca55221d54c50645bf253c52ec10725f757db3 100644 --- a/registry/protocol/protocol_test.go +++ b/registry/protocol/protocol_test.go @@ -59,7 +59,7 @@ func referNormal(t *testing.T, regProtocol *registryProtocol) { common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) - url.SubURL = &suburl + url.SubURL = suburl invoker := regProtocol.Refer(url) assert.IsType(t, &protocol.BaseInvoker{}, invoker) @@ -84,7 +84,7 @@ func TestMultiRegRefer(t *testing.T) { common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) - url2.SubURL = &suburl2 + url2.SubURL = suburl2 regProtocol.Refer(url2) var count int @@ -105,7 +105,7 @@ func TestOneRegRefer(t *testing.T) { common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) - url2.SubURL = &suburl2 + url2.SubURL = suburl2 regProtocol.Refer(url2) var count int @@ -128,13 +128,13 @@ func exporterNormal(t *testing.T, regProtocol *registryProtocol) *common.URL { common.WithParamsValue(constant.VERSION_KEY, "1.0.0"), ) - url.SubURL = &suburl + url.SubURL = suburl invoker := protocol.NewBaseInvoker(url) exporter := regProtocol.Export(invoker) assert.IsType(t, &protocol.BaseExporter{}, exporter) assert.Equal(t, exporter.GetInvoker().GetUrl().String(), suburl.String()) - return &url + return url } func TestExporter(t *testing.T) { @@ -153,7 +153,7 @@ func TestMultiRegAndMultiProtoExporter(t *testing.T) { common.WithParamsValue(constant.CLUSTER_KEY, "mock"), ) - url2.SubURL = &suburl2 + url2.SubURL = suburl2 invoker2 := protocol.NewBaseInvoker(url2) regProtocol.Export(invoker2) @@ -184,7 +184,7 @@ func TestOneRegAndProtoExporter(t *testing.T) { common.WithParamsValue(constant.VERSION_KEY, "1.0.0"), ) - url2.SubURL = &suburl2 + url2.SubURL = suburl2 invoker2 := protocol.NewBaseInvoker(url2) regProtocol.Export(invoker2) @@ -253,7 +253,7 @@ func TestExportWithOverrideListener(t *testing.T) { func TestExportWithServiceConfig(t *testing.T) { extension.SetDefaultConfigurator(configurator.NewMockConfigurator) ccUrl, _ := common.NewURL("mock://127.0.0.1:1111") - dc, _ := (&config_center.MockDynamicConfigurationFactory{}).GetDynamicConfiguration(&ccUrl) + dc, _ := (&config_center.MockDynamicConfigurationFactory{}).GetDynamicConfiguration(ccUrl) common_cfg.GetEnvInstance().SetDynamicConfiguration(dc) regProtocol := newRegistryProtocol() url := exporterNormal(t, regProtocol) @@ -265,6 +265,7 @@ func TestExportWithServiceConfig(t *testing.T) { newUrl := url.SubURL.Clone() newUrl.SetParam(constant.CLUSTER_KEY, "mock1") + v2, _ := regProtocol.bounds.Load(getCacheKey(newUrl)) assert.NotNil(t, v2) } @@ -272,7 +273,7 @@ func TestExportWithServiceConfig(t *testing.T) { func TestExportWithApplicationConfig(t *testing.T) { extension.SetDefaultConfigurator(configurator.NewMockConfigurator) ccUrl, _ := common.NewURL("mock://127.0.0.1:1111") - dc, _ := (&config_center.MockDynamicConfigurationFactory{}).GetDynamicConfiguration(&ccUrl) + dc, _ := (&config_center.MockDynamicConfigurationFactory{}).GetDynamicConfiguration(ccUrl) common_cfg.GetEnvInstance().SetDynamicConfiguration(dc) regProtocol := newRegistryProtocol() url := exporterNormal(t, regProtocol) @@ -290,7 +291,7 @@ func TestExportWithApplicationConfig(t *testing.T) { 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) + 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 5e77eab186680671f27b44bbe2e6a6b964a28721..439178390a2fcedba3bd1c9919d9a797f44e3a21 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -30,38 +30,54 @@ import ( // Registry Extension - Registry type Registry interface { common.Node - //used for service provider calling , register services to registry - //And it is also used for service consumer calling , register services cared about ,for dubbo's admin monitoring. - Register(url common.URL) error + + // Register is used for service provider calling, register services + // to registry. 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. + // 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(event.URL) (Listener, error) + // 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 - //Will replace mode1 in dubbogo version v1.1.0 - //mode2 : callback mode, subscribe with notify(notify listener). + // Subscribe is required to support the contract: + // 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(event.URL) (Listener, error) + // Will replace mode1 in dubbogo version v1.1.0 + // mode2: callback mode, subscribe with notify(notify listener). 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 + // url Subscription condition, not allowed to be empty, e.g. + // consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin // listener A listener of the change event, not allowed to be empty UnSubscribe(*common.URL, NotifyListener) error } // nolint type NotifyListener interface { - // Notify supports notifications on the service interface and the dimension of the data type. + // Notify supports notifications on the service interface and the dimension of the data type. When a list of + // events are passed in, it's considered as a complete list, on the other side, if one single event is + // passed in, then it's a incremental event. Pls. note when a list (instead of single event) comes, + // the impl of NotifyListener may abandon the accumulated result from previous notifications. Notify(*ServiceEvent) + // NotifyAll the events are complete Service Event List. + // The argument of events []*ServiceEvent is equal to urls []*URL, The Action of serviceEvent should be EventTypeUpdate. + // If your registry center can only get all urls but can't get individual event, you should use this one. + // After notify the address, the callback func will be invoked. + NotifyAll([]*ServiceEvent, func()) } // Listener Deprecated! diff --git a/registry/servicediscovery/instance/random/random_service_instance_selector.go b/registry/servicediscovery/instance/random/random_service_instance_selector.go index 3f8f30dc8e9e91f9c75f8ff0611c98bb2f0c7b85..7e4e0eefbbe7f9ce39ee5da1e9f6a9d8ad61c737 100644 --- a/registry/servicediscovery/instance/random/random_service_instance_selector.go +++ b/registry/servicediscovery/instance/random/random_service_instance_selector.go @@ -41,7 +41,7 @@ func NewRandomServiceInstanceSelector() instance.ServiceInstanceSelector { return &RandomServiceInstanceSelector{} } -func (r *RandomServiceInstanceSelector) Select(url common.URL, serviceInstances []registry.ServiceInstance) registry.ServiceInstance { +func (r *RandomServiceInstanceSelector) Select(url *common.URL, serviceInstances []registry.ServiceInstance) registry.ServiceInstance { if len(serviceInstances) == 0 { return nil } diff --git a/registry/servicediscovery/instance/random/random_service_instance_selector_test.go b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go index cddeb42c904131cdc6a62e5142de850410a3ec5a..c53c058be912ba6fd2a66e42883914fdf1d60eb1 100644 --- a/registry/servicediscovery/instance/random/random_service_instance_selector_test.go +++ b/registry/servicediscovery/instance/random/random_service_instance_selector_test.go @@ -52,5 +52,5 @@ func TestRandomServiceInstanceSelector_Select(t *testing.T) { Metadata: nil, }, } - assert.NotNil(t, selector.Select(common.URL{}, serviceInstances)) + assert.NotNil(t, selector.Select(&common.URL{}, serviceInstances)) } diff --git a/registry/servicediscovery/instance/service_instance_selector.go b/registry/servicediscovery/instance/service_instance_selector.go index 82fb3458be2838e9a5780e95be71aa89039b664f..5690ab6c9013d82b1a6ee87797dd7e424e5c07b6 100644 --- a/registry/servicediscovery/instance/service_instance_selector.go +++ b/registry/servicediscovery/instance/service_instance_selector.go @@ -24,5 +24,5 @@ import ( type ServiceInstanceSelector interface { //Select an instance of ServiceInstance by the specified ServiceInstance service instances - Select(url common.URL, serviceInstances []registry.ServiceInstance) registry.ServiceInstance + 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 index 7576804eb563e16a043f63f17db2532f48c878f1..ad6ec981ded9e224cecc0fa37c8e7f8c0254a932 100644 --- a/registry/servicediscovery/service_discovery_registry.go +++ b/registry/servicediscovery/service_discovery_registry.go @@ -26,7 +26,6 @@ import ( ) import ( - cm "github.com/Workiva/go-datastructures/common" gxset "github.com/dubbogo/gost/container/set" perrors "github.com/pkg/errors" "go.uber.org/atomic" @@ -70,12 +69,12 @@ type serviceDiscoveryRegistry struct { metaDataService service.MetadataService registeredListeners *gxset.HashSet subscribedURLsSynthesizers []synthesizer.SubscribedURLsSynthesizer - serviceRevisionExportedURLsCache map[string]map[string][]common.URL + serviceRevisionExportedURLsCache map[string]map[string][]*common.URL } func newServiceDiscoveryRegistry(url *common.URL) (registry.Registry, error) { - tryInitMetadataService() + tryInitMetadataService(url) serviceDiscovery, err := creatServiceDiscovery(url) if err != nil { @@ -94,13 +93,13 @@ func newServiceDiscoveryRegistry(url *common.URL) (registry.Registry, error) { subscribedServices: subscribedServices, subscribedURLsSynthesizers: subscribedURLsSynthesizers, registeredListeners: gxset.NewSet(), - serviceRevisionExportedURLsCache: make(map[string]map[string][]common.URL, 8), + serviceRevisionExportedURLsCache: make(map[string]map[string][]*common.URL, 8), serviceNameMapping: serviceNameMapping, metaDataService: metaDataService, }, nil } -func (s *serviceDiscoveryRegistry) UnRegister(url common.URL) error { +func (s *serviceDiscoveryRegistry) UnRegister(url *common.URL) error { if !shouldRegister(url) { return nil } @@ -108,10 +107,10 @@ func (s *serviceDiscoveryRegistry) UnRegister(url common.URL) error { } func (s *serviceDiscoveryRegistry) UnSubscribe(url *common.URL, listener registry.NotifyListener) error { - if !shouldSubscribe(*url) { + if !shouldSubscribe(url) { return nil } - return s.metaDataService.UnsubscribeURL(*url) + return s.metaDataService.UnsubscribeURL(url) } func creatServiceDiscovery(url *common.URL) (registry.ServiceDiscovery, error) { @@ -145,8 +144,8 @@ func (s *serviceDiscoveryRegistry) GetServiceDiscovery() registry.ServiceDiscove return s.serviceDiscovery } -func (s *serviceDiscoveryRegistry) GetUrl() common.URL { - return *s.url +func (s *serviceDiscoveryRegistry) GetUrl() *common.URL { + return s.url } func (s *serviceDiscoveryRegistry) IsAvailable() bool { @@ -161,7 +160,7 @@ func (s *serviceDiscoveryRegistry) Destroy() { } } -func (s *serviceDiscoveryRegistry) Register(url common.URL) error { +func (s *serviceDiscoveryRegistry) Register(url *common.URL) error { if !shouldRegister(url) { return nil } @@ -185,7 +184,7 @@ func (s *serviceDiscoveryRegistry) Register(url common.URL) error { url.Protocol) } -func shouldRegister(url common.URL) bool { +func shouldRegister(url *common.URL) bool { side := url.GetParam(constant.SIDE_KEY, "") if side == constant.PROVIDER_PROTOCOL { return true @@ -195,14 +194,14 @@ func shouldRegister(url common.URL) bool { } func (s *serviceDiscoveryRegistry) Subscribe(url *common.URL, notify registry.NotifyListener) error { - if !shouldSubscribe(*url) { + if !shouldSubscribe(url) { return nil } - _, err := s.metaDataService.SubscribeURL(*url) + _, err := s.metaDataService.SubscribeURL(url) if err != nil { return perrors.WithMessage(err, "subscribe url error: "+url.String()) } - services := s.getServices(*url) + 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()) @@ -218,12 +217,12 @@ func (s *serviceDiscoveryRegistry) Subscribe(url *common.URL, notify registry.No serviceDiscoveryRegistry: s, }, } - s.registerServiceInstancesChangedListener(*url, listener) + s.registerServiceInstancesChangedListener(url, listener) } return nil } -func (s *serviceDiscoveryRegistry) registerServiceInstancesChangedListener(url common.URL, listener *registry.ServiceInstancesChangedListener) { +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) @@ -234,7 +233,7 @@ func (s *serviceDiscoveryRegistry) registerServiceInstancesChangedListener(url c } -func getUrlKey(url common.URL) string { +func getUrlKey(url *common.URL) string { var bf bytes.Buffer if len(url.Protocol) != 0 { bf.WriteString(url.Protocol) @@ -256,7 +255,7 @@ func getUrlKey(url common.URL) string { return bf.String() } -func appendParam(buffer bytes.Buffer, paramKey string, url common.URL) { +func appendParam(buffer bytes.Buffer, paramKey string, url *common.URL) { buffer.WriteString(paramKey) buffer.WriteString("=") buffer.WriteString(url.GetParam(paramKey, "")) @@ -268,8 +267,8 @@ func (s *serviceDiscoveryRegistry) subscribe(url *common.URL, notify registry.No logger.Warnf("here is no instance in service[name : %s]", serviceName) return } - var subscribedURLs []common.URL - subscribedURLs = append(subscribedURLs, s.getExportedUrls(*url, serviceInstances)...) + var subscribedURLs []*common.URL + subscribedURLs = append(subscribedURLs, s.getExportedUrls(url, serviceInstances)...) if len(subscribedURLs) == 0 { subscribedURLs = s.synthesizeSubscribedURLs(url, serviceInstances) } @@ -282,8 +281,8 @@ func (s *serviceDiscoveryRegistry) subscribe(url *common.URL, notify registry.No } -func (s *serviceDiscoveryRegistry) synthesizeSubscribedURLs(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL { - var urls []common.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)...) @@ -292,11 +291,11 @@ func (s *serviceDiscoveryRegistry) synthesizeSubscribedURLs(subscribedURL *commo return urls } -func shouldSubscribe(url common.URL) bool { +func shouldSubscribe(url *common.URL) bool { return !shouldRegister(url) } -func (s *serviceDiscoveryRegistry) getServices(url common.URL) *gxset.HashSet { +func (s *serviceDiscoveryRegistry) getServices(url *common.URL) *gxset.HashSet { services := gxset.NewSet() serviceNames := url.GetParam(constant.PROVIDER_BY, "") if len(serviceNames) > 0 { @@ -311,7 +310,7 @@ func (s *serviceDiscoveryRegistry) getServices(url common.URL) *gxset.HashSet { return services } -func (s *serviceDiscoveryRegistry) findMappedServices(url common.URL) *gxset.HashSet { +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, "") @@ -325,7 +324,7 @@ func (s *serviceDiscoveryRegistry) findMappedServices(url common.URL) *gxset.Has return serviceNames } -func (s *serviceDiscoveryRegistry) getExportedUrls(subscribedURL common.URL, serviceInstances []registry.ServiceInstance) []common.URL { +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() { @@ -340,32 +339,15 @@ func (s *serviceDiscoveryRegistry) getExportedUrls(subscribedURL common.URL, ser filterInstances = append(filterInstances, s) } if len(filterInstances) == 0 { - return []common.URL{} + 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 +func (s *serviceDiscoveryRegistry) getExportedUrlsByInst(serviceInstance registry.ServiceInstance) []*common.URL { + var urls []*common.URL metadataStorageType := getExportedStoreType(serviceInstance) proxyFactory := extension.GetMetadataServiceProxyFactory(metadataStorageType) if proxyFactory == nil { @@ -381,7 +363,7 @@ func (s *serviceDiscoveryRegistry) getExportedUrlsByInst(serviceInstance registr return urls } - ret := make([]common.URL, 0, len(result)) + ret := make([]*common.URL, 0, len(result)) for _, ui := range result { u, err := common.NewURL(ui.(string)) @@ -464,18 +446,18 @@ func (s *serviceDiscoveryRegistry) selectServiceInstance(serviceInstances []regi logger.Errorf("get service instance selector cathe error:%s", err.Error()) return nil } - return selector.Select(*s.url, serviceInstances) + return selector.Select(s.url, serviceInstances) } -func (s *serviceDiscoveryRegistry) initRevisionExportedURLsByInst(serviceInstance registry.ServiceInstance) []common.URL { +func (s *serviceDiscoveryRegistry) initRevisionExportedURLsByInst(serviceInstance registry.ServiceInstance) []*common.URL { if serviceInstance == nil { - return []common.URL{} + return nil } serviceName := serviceInstance.GetServiceName() revision := getExportedServicesRevision(serviceInstance) revisionExportedURLsMap := s.serviceRevisionExportedURLsCache[serviceName] if revisionExportedURLsMap == nil { - revisionExportedURLsMap = make(map[string][]common.URL, 4) + revisionExportedURLsMap = make(map[string][]*common.URL, 4) s.serviceRevisionExportedURLsCache[serviceName] = revisionExportedURLsMap } revisionExportedURLs := revisionExportedURLsMap[revision] @@ -521,11 +503,11 @@ func getExportedStoreType(serviceInstance registry.ServiceInstance) string { return result } -func (s *serviceDiscoveryRegistry) cloneExportedURLs(url common.URL, serviceInsances []registry.ServiceInstance) []common.URL { +func (s *serviceDiscoveryRegistry) cloneExportedURLs(url *common.URL, serviceInsances []registry.ServiceInstance) []*common.URL { if len(serviceInsances) == 0 { - return []common.URL{} + return []*common.URL{} } - var clonedExportedURLs []common.URL + var clonedExportedURLs []*common.URL removeParamSet := gxset.NewSet() removeParamSet.Add(constant.PID_KEY) removeParamSet.Add(constant.TIMESTAMP_KEY) @@ -540,7 +522,7 @@ func (s *serviceDiscoveryRegistry) cloneExportedURLs(url common.URL, serviceInsa } cloneUrl := u.CloneExceptParams(removeParamSet) - clonedExportedURLs = append(clonedExportedURLs, *cloneUrl) + clonedExportedURLs = append(clonedExportedURLs, cloneUrl) } } return clonedExportedURLs @@ -548,8 +530,8 @@ func (s *serviceDiscoveryRegistry) cloneExportedURLs(url common.URL, serviceInsa } type endpoint struct { - Port int `json:"port, omitempty"` - Protocol string `json:"protocol, omitempty"` + Port int `json:"port,omitempty"` + Protocol string `json:"protocol,omitempty"` } func getProtocolPort(serviceInstance registry.ServiceInstance, protocol string) int { @@ -571,38 +553,38 @@ func getProtocolPort(serviceInstance registry.ServiceInstance, protocol string) } return -1 } -func (s *serviceDiscoveryRegistry) getTemplateExportedURLs(url common.URL, serviceInstance registry.ServiceInstance) []common.URL { +func (s *serviceDiscoveryRegistry) getTemplateExportedURLs(url *common.URL, serviceInstance registry.ServiceInstance) []*common.URL { exportedURLs := s.getRevisionExportedURLs(serviceInstance) if len(exportedURLs) == 0 { - return []common.URL{} + return []*common.URL{} } return filterSubscribedURLs(url, exportedURLs) } -func (s *serviceDiscoveryRegistry) getRevisionExportedURLs(serviceInstance registry.ServiceInstance) []common.URL { +func (s *serviceDiscoveryRegistry) getRevisionExportedURLs(serviceInstance registry.ServiceInstance) []*common.URL { if serviceInstance == nil { - return []common.URL{} + return []*common.URL{} } serviceName := serviceInstance.GetServiceName() revision := getExportedServicesRevision(serviceInstance) s.lock.RLock() revisionExportedURLsMap, exist := s.serviceRevisionExportedURLsCache[serviceName] if !exist { - return []common.URL{} + return []*common.URL{} } exportedURLs, exist := revisionExportedURLsMap[revision] if !exist { - return []common.URL{} + 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)) + 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 +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 @@ -642,7 +624,7 @@ var ( // tryInitMetadataService will try to initialize metadata service // TODO (move to somewhere) -func tryInitMetadataService() { +func tryInitMetadataService(url *common.URL) { ms, err := extension.GetMetadataService(config.GetApplicationConfig().MetadataType) if err != nil { @@ -662,7 +644,7 @@ func tryInitMetadataService() { expt := configurable.NewMetadataServiceExporter(ms) - err = expt.Export() + err = expt.Export(url) if err != nil { logger.Errorf("could not export the metadata service", err) } diff --git a/registry/servicediscovery/service_discovery_registry_test.go b/registry/servicediscovery/service_discovery_registry_test.go index 53eb86507e635be32eb362519922f7042f945519..ad6b73d3b4da77e5fe21a3085cdc21d3eca0246d 100644 --- a/registry/servicediscovery/service_discovery_registry_test.go +++ b/registry/servicediscovery/service_discovery_registry_test.go @@ -76,7 +76,7 @@ func TestServiceDiscoveryRegistry_Register(t *testing.T) { "&service_discovery=mock" + "&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs" + "&side=provider") - registry, err := newServiceDiscoveryRegistry(®istryURL) + registry, err := newServiceDiscoveryRegistry(registryURL) assert.Nil(t, err) assert.NotNil(t, registry) registry.Register(url) @@ -85,19 +85,19 @@ func TestServiceDiscoveryRegistry_Register(t *testing.T) { type mockEventDispatcher struct { } -func (m *mockEventDispatcher) AddEventListener(listener observer.EventListener) { +func (m *mockEventDispatcher) AddEventListener(observer.EventListener) { } -func (m *mockEventDispatcher) AddEventListeners(listenersSlice []observer.EventListener) { +func (m *mockEventDispatcher) AddEventListeners([]observer.EventListener) { } -func (m *mockEventDispatcher) RemoveEventListener(listener observer.EventListener) { +func (m *mockEventDispatcher) RemoveEventListener(observer.EventListener) { panic("implement me") } -func (m *mockEventDispatcher) RemoveEventListeners(listenersSlice []observer.EventListener) { +func (m *mockEventDispatcher) RemoveEventListeners([]observer.EventListener) { panic("implement me") } @@ -109,17 +109,17 @@ func (m *mockEventDispatcher) RemoveAllEventListeners() { panic("implement me") } -func (m *mockEventDispatcher) Dispatch(event observer.Event) { +func (m *mockEventDispatcher) Dispatch(observer.Event) { } type mockServiceNameMapping struct { } -func (m *mockServiceNameMapping) Map(serviceInterface string, group string, version string, protocol string) error { +func (m *mockServiceNameMapping) Map(string, string, string, string) error { return nil } -func (m *mockServiceNameMapping) Get(serviceInterface string, group string, version string, protocol string) (*gxset.HashSet, error) { +func (m *mockServiceNameMapping) Get(string, string, string, string) (*gxset.HashSet, error) { panic("implement me") } @@ -134,15 +134,15 @@ func (m *mockServiceDiscovery) Destroy() error { panic("implement me") } -func (m *mockServiceDiscovery) Register(instance registry.ServiceInstance) error { +func (m *mockServiceDiscovery) Register(registry.ServiceInstance) error { return nil } -func (m *mockServiceDiscovery) Update(instance registry.ServiceInstance) error { +func (m *mockServiceDiscovery) Update(registry.ServiceInstance) error { panic("implement me") } -func (m *mockServiceDiscovery) Unregister(instance registry.ServiceInstance) error { +func (m *mockServiceDiscovery) Unregister(registry.ServiceInstance) error { panic("implement me") } @@ -154,35 +154,35 @@ func (m *mockServiceDiscovery) GetServices() *gxset.HashSet { panic("implement me") } -func (m *mockServiceDiscovery) GetInstances(serviceName string) []registry.ServiceInstance { +func (m *mockServiceDiscovery) GetInstances(string) []registry.ServiceInstance { panic("implement me") } -func (m *mockServiceDiscovery) GetInstancesByPage(serviceName string, offset int, pageSize int) gxpage.Pager { +func (m *mockServiceDiscovery) GetInstancesByPage(string, int, int) gxpage.Pager { panic("implement me") } -func (m *mockServiceDiscovery) GetHealthyInstancesByPage(serviceName string, offset int, pageSize int, healthy bool) gxpage.Pager { +func (m *mockServiceDiscovery) GetHealthyInstancesByPage(string, int, int, bool) gxpage.Pager { panic("implement me") } -func (m *mockServiceDiscovery) GetRequestInstances(serviceNames []string, offset int, requestedSize int) map[string]gxpage.Pager { +func (m *mockServiceDiscovery) GetRequestInstances([]string, int, int) map[string]gxpage.Pager { panic("implement me") } -func (m *mockServiceDiscovery) AddListener(listener *registry.ServiceInstancesChangedListener) error { +func (m *mockServiceDiscovery) AddListener(*registry.ServiceInstancesChangedListener) error { panic("implement me") } -func (m *mockServiceDiscovery) DispatchEventByServiceName(serviceName string) error { +func (m *mockServiceDiscovery) DispatchEventByServiceName(string) error { panic("implement me") } -func (m *mockServiceDiscovery) DispatchEventForInstances(serviceName string, instances []registry.ServiceInstance) error { +func (m *mockServiceDiscovery) DispatchEventForInstances(string, []registry.ServiceInstance) error { panic("implement me") } -func (m *mockServiceDiscovery) DispatchEvent(event *registry.ServiceInstancesChangedEvent) error { +func (m *mockServiceDiscovery) DispatchEvent(*registry.ServiceInstancesChangedEvent) error { panic("implement me") } @@ -197,27 +197,27 @@ func (m *mockMetadataService) ServiceName() (string, error) { panic("implement me") } -func (m *mockMetadataService) ExportURL(url common.URL) (bool, error) { +func (m *mockMetadataService) ExportURL(*common.URL) (bool, error) { return true, nil } -func (m *mockMetadataService) UnexportURL(url common.URL) error { +func (m *mockMetadataService) UnexportURL(*common.URL) error { panic("implement me") } -func (m *mockMetadataService) SubscribeURL(url common.URL) (bool, error) { +func (m *mockMetadataService) SubscribeURL(*common.URL) (bool, error) { panic("implement me") } -func (m *mockMetadataService) UnsubscribeURL(url common.URL) error { +func (m *mockMetadataService) UnsubscribeURL(*common.URL) error { panic("implement me") } -func (m *mockMetadataService) PublishServiceDefinition(url common.URL) error { +func (m *mockMetadataService) PublishServiceDefinition(*common.URL) error { return nil } -func (m *mockMetadataService) GetExportedURLs(serviceInterface string, group string, version string, protocol string) ([]interface{}, error) { +func (m *mockMetadataService) GetExportedURLs(string, string, string, string) ([]interface{}, error) { panic("implement me") } @@ -225,19 +225,19 @@ func (m *mockMetadataService) MethodMapper() map[string]string { panic("implement me") } -func (m *mockMetadataService) GetSubscribedURLs() ([]common.URL, error) { +func (m *mockMetadataService) GetSubscribedURLs() ([]*common.URL, error) { panic("implement me") } -func (m *mockMetadataService) GetServiceDefinition(interfaceName string, group string, version string) (string, error) { +func (m *mockMetadataService) GetServiceDefinition(string, string, string) (string, error) { panic("implement me") } -func (m *mockMetadataService) GetServiceDefinitionByServiceKey(serviceKey string) (string, error) { +func (m *mockMetadataService) GetServiceDefinitionByServiceKey(string) (string, error) { panic("implement me") } -func (m *mockMetadataService) RefreshMetadata(exportedRevision string, subscribedRevision string) (bool, error) { +func (m *mockMetadataService) RefreshMetadata(string, string) (bool, error) { panic("implement me") } diff --git a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go index 086a26de58f8472e35e07a8a174fdee86afa82f2..d1ab6113b31a779529cc7e33ffd0f14233dd9ad6 100644 --- a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go +++ b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer.go @@ -44,8 +44,8 @@ func (r RestSubscribedURLsSynthesizer) Support(subscribedURL *common.URL) bool { return false } -func (r RestSubscribedURLsSynthesizer) Synthesize(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []common.URL { - urls := make([]common.URL, len(serviceInstances), len(serviceInstances)) +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]), @@ -55,7 +55,7 @@ func (r RestSubscribedURLsSynthesizer) Synthesize(subscribedURL *common.URL, ser common.WithParamsValue(constant.APPLICATION_KEY, s.GetServiceName()), common.WithParamsValue(constant.REGISTRY_KEY, "true"), ) - urls[i] = *u + urls[i] = u } return urls } diff --git a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go index b52cc2323d6f9ae1bca8cfd1a4c5217af5e25f12..1bb38c92e32942fb1a39bbaeb9617013459bcdfe 100644 --- a/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go +++ b/registry/servicediscovery/synthesizer/rest/rest_subscribed_urls_synthesizer_test.go @@ -56,7 +56,7 @@ func TestRestSubscribedURLsSynthesizer_Synthesize(t *testing.T) { }, } - var expectUrls []common.URL + 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{}), @@ -69,7 +69,7 @@ func TestRestSubscribedURLsSynthesizer_Synthesize(t *testing.T) { 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) + 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 index 415ca35fbad2fa335d687dc7a7718fa3a4b2b487..557c86ec127fe88411fdbe9dd83d43ff8cd18cec 100644 --- a/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go +++ b/registry/servicediscovery/synthesizer/subscribed_urls_synthesizer.go @@ -27,5 +27,5 @@ 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 + Synthesize(subscribedURL *common.URL, serviceInstances []registry.ServiceInstance) []*common.URL } diff --git a/registry/zookeeper/listener.go b/registry/zookeeper/listener.go index ec82fa0309118fba4b5c21772d4dfd356f3b0c5c..88109fcf52ec27b87f6b1ddf2694a14576272fbe 100644 --- a/registry/zookeeper/listener.go +++ b/registry/zookeeper/listener.go @@ -104,6 +104,7 @@ func (l *RegistryDataListener) DataChange(eventType remoting.Event) bool { func (l *RegistryDataListener) Close() { l.mutex.Lock() defer l.mutex.Unlock() + l.closed = true for _, listener := range l.subscribed { listener.(*RegistryConfigurationListener).Close() } @@ -158,7 +159,7 @@ func (l *RegistryConfigurationListener) Next() (*registry.ServiceEvent, error) { //r.update(e.res) //write to invoker //r.outerEventCh <- e.res - return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(common.URL)}, nil + return ®istry.ServiceEvent{Action: e.ConfigType, Service: e.Value.(*common.URL)}, nil } } } diff --git a/registry/zookeeper/listener_test.go b/registry/zookeeper/listener_test.go index a0e9147a9e0ee8767efcf78d5e2aa536140f6a8b..20ec1cf69a65601e5dd8351083ce23edfb67d10c 100644 --- a/registry/zookeeper/listener_test.go +++ b/registry/zookeeper/listener_test.go @@ -34,7 +34,7 @@ import ( func Test_DataChange(t *testing.T) { 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.SubscribeURL(&url, &MockConfigurationListener{}) + 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) } diff --git a/registry/zookeeper/registry.go b/registry/zookeeper/registry.go index e8ee51beb70b5a08ec60b213c5342ef52972c59f..fe492c2b12712e0935ccd6fdd264e0fe8f24e213 100644 --- a/registry/zookeeper/registry.go +++ b/registry/zookeeper/registry.go @@ -127,15 +127,15 @@ func (r *zkRegistry) InitListeners() { oldDataListener := r.dataListener oldDataListener.mutex.Lock() defer oldDataListener.mutex.Unlock() - recoverd := r.dataListener.subscribed - if recoverd != nil && len(recoverd) > 0 { + r.dataListener.closed = true + recovered := r.dataListener.subscribed + if recovered != nil && len(recovered) > 0 { // recover all subscribed url - for _, oldListener := range recoverd { + for _, oldListener := range recovered { var ( regConfigListener *RegistryConfigurationListener ok bool ) - if regConfigListener, ok = oldListener.(*RegistryConfigurationListener); ok { regConfigListener.Close() } @@ -212,6 +212,9 @@ func (r *zkRegistry) registerTempZookeeperNode(root string, node string) error { r.cltLock.Lock() defer r.cltLock.Unlock() + if r.client == nil { + return perrors.WithStack(perrors.New("zk client already been closed")) + } err = r.client.Create(root) if err != nil { logger.Errorf("zk.Create(root{%s}) = err{%v}", root, perrors.WithStack(err)) @@ -292,10 +295,10 @@ func (r *zkRegistry) getCloseListener(conf *common.URL) (*RegistryConfigurationL r.dataListener.mutex.Lock() configurationListener := r.dataListener.subscribed[conf.ServiceKey()] if configurationListener != nil { - zkListener, _ := configurationListener.(*RegistryConfigurationListener) if zkListener != nil { if zkListener.isClosed { + r.dataListener.mutex.Unlock() return nil, perrors.New("configListener already been closed") } } diff --git a/registry/zookeeper/registry_test.go b/registry/zookeeper/registry_test.go index d915fc2ce10359f0dd1970daf019746ce066f511..e630db7e41092e48592bf99690f11863a23e23d3 100644 --- a/registry/zookeeper/registry_test.go +++ b/registry/zookeeper/registry_test.go @@ -37,7 +37,7 @@ func Test_Register(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.WithParamsValue("serviceid", "soa.mock"), common.WithMethods([]string{"GetUser", "AddUser"})) - ts, reg, _ := newMockZkRegistry(®url) + ts, reg, _ := newMockZkRegistry(regurl) defer ts.Stop() err := reg.Register(url) children, _ := reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") @@ -50,7 +50,7 @@ func Test_UnRegister(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.WithParamsValue("serviceid", "soa.mock"), common.WithMethods([]string{"GetUser", "AddUser"})) - ts, reg, _ := newMockZkRegistry(®url) + ts, reg, _ := newMockZkRegistry(regurl) defer ts.Stop() err := reg.Register(url) children, _ := reg.client.GetChildren("/dubbo/com.ikurento.user.UserProvider/providers") @@ -73,7 +73,7 @@ func Test_UnRegister(t *testing.T) { 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"})) - ts, reg, _ := newMockZkRegistry(®url) + ts, reg, _ := newMockZkRegistry(regurl) //provider register err := reg.Register(url) @@ -85,10 +85,10 @@ func Test_Subscribe(t *testing.T) { //consumer register regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) - _, reg2, _ := newMockZkRegistry(®url, zookeeper.WithTestCluster(ts)) + _, reg2, _ := newMockZkRegistry(regurl, zookeeper.WithTestCluster(ts)) reg2.Register(url) - listener, _ := reg2.DoSubscribe(&url) + listener, _ := reg2.DoSubscribe(url) serviceEvent, _ := listener.Next() assert.NoError(t, err) @@ -102,7 +102,7 @@ func Test_Subscribe(t *testing.T) { 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) + ts, reg, _ := newMockZkRegistry(regurl) //provider register err := reg.Register(url) @@ -114,10 +114,10 @@ func Test_UnSubscribe(t *testing.T) { //consumer register regurl.SetParam(constant.ROLE_KEY, strconv.Itoa(common.CONSUMER)) - _, reg2, _ := newMockZkRegistry(®url, zookeeper.WithTestCluster(ts)) + _, reg2, _ := newMockZkRegistry(regurl, zookeeper.WithTestCluster(ts)) reg2.Register(url) - listener, _ := reg2.DoSubscribe(&url) + listener, _ := reg2.DoSubscribe(url) serviceEvent, _ := listener.Next() assert.NoError(t, err) @@ -126,7 +126,7 @@ func Test_UnSubscribe(t *testing.T) { } assert.Regexp(t, ".*ServiceEvent{Action{add}.*", serviceEvent.String()) - reg2.UnSubscribe(&url, nil) + reg2.UnSubscribe(url, nil) assert.Nil(t, reg2.listener) defer ts.Stop() @@ -136,13 +136,13 @@ 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"})) - ts, reg, err := newMockZkRegistry(®url) + ts, reg, err := newMockZkRegistry(regurl) defer ts.Stop() assert.NoError(t, err) err = reg.Register(url) assert.NoError(t, err) - _, err = reg.DoSubscribe(&url) + _, err = reg.DoSubscribe(url) assert.NoError(t, err) //listener.Close() @@ -156,7 +156,7 @@ func Test_ProviderDestory(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, err := newMockZkRegistry(®url) + ts, reg, err := newMockZkRegistry(regurl) defer ts.Stop() assert.NoError(t, err) diff --git a/registry/zookeeper/service_discovery.go b/registry/zookeeper/service_discovery.go index 5ad83ef90947afc0a5ca75af5009e8b55b4f6627..6d9582f33a7b2517c4edc96d00d00ad6b57a4835 100644 --- a/registry/zookeeper/service_discovery.go +++ b/registry/zookeeper/service_discovery.go @@ -154,8 +154,8 @@ func (zksd *zookeeperServiceDiscovery) RestartCallBack() bool { } // nolint -func (zksd *zookeeperServiceDiscovery) GetUrl() common.URL { - return *zksd.url +func (zksd *zookeeperServiceDiscovery) GetUrl() *common.URL { + return zksd.url } // nolint diff --git a/remoting/codec.go b/remoting/codec.go new file mode 100644 index 0000000000000000000000000000000000000000..607d1643cc1967e93bf5288d8d4c0788c73a735e --- /dev/null +++ b/remoting/codec.go @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package remoting + +import ( + "bytes" +) + +// codec for exchangeClient +type Codec interface { + EncodeRequest(request *Request) (*bytes.Buffer, error) + EncodeResponse(response *Response) (*bytes.Buffer, error) + Decode(data []byte) (DecodeResult, int, error) +} + +type DecodeResult struct { + IsRequest bool + Result interface{} +} + +var ( + codec = make(map[string]Codec, 2) +) + +func RegistryCodec(protocol string, codecTmp Codec) { + codec[protocol] = codecTmp +} + +func GetCodec(protocol string) Codec { + return codec[protocol] +} diff --git a/remoting/etcdv3/client.go b/remoting/etcdv3/client.go index 4e7436e445f652d8fe1db2991c87303653beb9ec..ebd454242d49ee82c81fe1a1fae1a19980c238a4 100644 --- a/remoting/etcdv3/client.go +++ b/remoting/etcdv3/client.go @@ -408,7 +408,8 @@ func (c *Client) keepAliveKV(k string, v string) error { return ErrNilETCDV3Client } - lease, err := c.rawClient.Grant(c.ctx, int64(time.Second.Seconds())) + // make lease time longer, since 1 second is too short + lease, err := c.rawClient.Grant(c.ctx, int64(30*time.Second.Seconds())) if err != nil { return perrors.WithMessage(err, "grant lease") } diff --git a/remoting/etcdv3/facade.go b/remoting/etcdv3/facade.go index 52b1cce3e4618e9c2669e7a3b37256ebe6d61c41..614ba9ae3a1407daea2d9d534a0474a28ad8cac9 100644 --- a/remoting/etcdv3/facade.go +++ b/remoting/etcdv3/facade.go @@ -63,7 +63,7 @@ LOOP: r.ClientLock().Lock() clientName := RegistryETCDV3Client timeout, _ := time.ParseDuration(r.GetUrl().GetParam(constant.REGISTRY_TIMEOUT_KEY, constant.DEFAULT_REG_TIMEOUT)) - endpoint := r.GetUrl().Location + endpoints := r.Client().endpoints r.Client().Close() r.SetClient(nil) r.ClientLock().Unlock() @@ -80,11 +80,11 @@ LOOP: err = ValidateClient( r, WithName(clientName), - WithEndpoints(endpoint), + WithEndpoints(endpoints...), WithTimeout(timeout), ) logger.Infof("ETCDV3ProviderRegistry.validateETCDV3Client(etcd Addr{%s}) = error{%#v}", - endpoint, perrors.WithStack(err)) + endpoints, perrors.WithStack(err)) if err == nil && r.RestartCallBack() { break } diff --git a/remoting/etcdv3/listener.go b/remoting/etcdv3/listener.go index 4f80a89dfb713036a5d4d812bc7a2d5551f42284..c66928a6367cb2449de79a51b59d122a74a79911 100644 --- a/remoting/etcdv3/listener.go +++ b/remoting/etcdv3/listener.go @@ -129,8 +129,6 @@ func (l *EventListener) handleEvents(event *clientv3.Event, listeners ...remotin default: return false } - - panic("unreachable") } // ListenServiceNodeEventWithPrefix listens on a set of key with spec prefix diff --git a/remoting/exchange.go b/remoting/exchange.go new file mode 100644 index 0000000000000000000000000000000000000000..5fbd8ae9b449103d5f7e3b19db19bc6a9c73593d --- /dev/null +++ b/remoting/exchange.go @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package remoting + +import ( + "sync" + "time" +) + +import ( + "go.uber.org/atomic" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" +) + +var ( + // generate request ID for global use + sequence atomic.Int64 + + // store requestID and response + pendingResponses = new(sync.Map) +) + +type SequenceType int64 + +func init() { + // init request ID + sequence.Store(0) +} + +func SequenceId() int64 { + // increse 2 for every request as the same before. + // We expect that the request from client to server, the requestId is even; but from server to client, the requestId is odd. + return sequence.Add(2) +} + +// this is request for transport layer +type Request struct { + ID int64 + // protocol version + Version string + // serial ID (ignore) + SerialID byte + // Data + Data interface{} + TwoWay bool + Event bool +} + +// NewRequest aims to create Request. +// The ID is auto increase. +func NewRequest(version string) *Request { + return &Request{ + ID: SequenceId(), + Version: version, + } +} + +// this is response for transport layer +type Response struct { + ID int64 + Version string + SerialID byte + Status uint8 + Event bool + Error error + Result interface{} +} + +// NewResponse create to a new Response. +func NewResponse(id int64, version string) *Response { + return &Response{ + ID: id, + Version: version, + } +} + +// the response is heartbeat +func (response *Response) IsHeartbeat() bool { + return response.Event && response.Result == nil +} + +func (response *Response) Handle() { + pendingResponse := removePendingResponse(SequenceType(response.ID)) + if pendingResponse == nil { + logger.Errorf("failed to get pending response context for response package %s", *response) + return + } + + pendingResponse.response = response + + if pendingResponse.Callback == nil { + pendingResponse.Err = pendingResponse.response.Error + close(pendingResponse.Done) + } else { + pendingResponse.Callback(pendingResponse.GetCallResponse()) + } +} + +type Options struct { + // connect timeout + ConnectTimeout time.Duration +} + +//AsyncCallbackResponse async response for dubbo +type AsyncCallbackResponse struct { + common.CallbackResponse + Opts Options + Cause error + Start time.Time // invoke(call) start time == write start time + ReadStart time.Time // read start time, write duration = ReadStart - Start + Reply interface{} +} + +// the client sends requst to server, there is one pendingResponse at client side to wait the response from server +type PendingResponse struct { + seq int64 + Err error + start time.Time + ReadStart time.Time + Callback common.AsyncCallback + response *Response + Reply interface{} + Done chan struct{} +} + +// NewPendingResponse aims to create PendingResponse. +// Id is always from ID of Request +func NewPendingResponse(id int64) *PendingResponse { + return &PendingResponse{ + seq: id, + start: time.Now(), + response: &Response{}, + Done: make(chan struct{}), + } +} + +func (r *PendingResponse) SetResponse(response *Response) { + r.response = response +} + +// GetCallResponse is used for callback of async. +// It is will return AsyncCallbackResponse. +func (r PendingResponse) GetCallResponse() common.CallbackResponse { + return AsyncCallbackResponse{ + Cause: r.Err, + Start: r.start, + ReadStart: r.ReadStart, + Reply: r.response, + } +} + +// store response into map +func AddPendingResponse(pr *PendingResponse) { + pendingResponses.Store(SequenceType(pr.seq), pr) +} + +// get and remove response +func removePendingResponse(seq SequenceType) *PendingResponse { + if pendingResponses == nil { + return nil + } + if presp, ok := pendingResponses.Load(seq); ok { + pendingResponses.Delete(seq) + return presp.(*PendingResponse) + } + return nil +} + +// get response +func GetPendingResponse(seq SequenceType) *PendingResponse { + if presp, ok := pendingResponses.Load(seq); ok { + return presp.(*PendingResponse) + } + return nil +} diff --git a/remoting/exchange_client.go b/remoting/exchange_client.go new file mode 100644 index 0000000000000000000000000000000000000000..d65382035e397cc44d4cdcc2676be4bb7df3d02c --- /dev/null +++ b/remoting/exchange_client.go @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package remoting + +import ( + "errors" + "time" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol" +) + +// It is interface of client for network communication. +// If you use getty as network communication, you should define GettyClient that implements this interface. +type Client interface { + SetExchangeClient(client *ExchangeClient) + // connect url + Connect(url *common.URL) error + // close + Close() + // send request to server. + Request(request *Request, timeout time.Duration, response *PendingResponse) error + // check if the client is still available + IsAvailable() bool +} + +// This is abstraction level. it is like facade. +type ExchangeClient struct { + // connect server timeout + ConnectTimeout time.Duration + // to dial server address. The format: ip:port + address string + // the client that will deal with the transport. It is interface, and it will use gettyClient by default. + client Client + // the tag for init. + init bool +} + +// create ExchangeClient +func NewExchangeClient(url *common.URL, client Client, connectTimeout time.Duration, lazyInit bool) *ExchangeClient { + exchangeClient := &ExchangeClient{ + ConnectTimeout: connectTimeout, + address: url.Location, + client: client, + } + client.SetExchangeClient(exchangeClient) + if !lazyInit { + if err := exchangeClient.doInit(url); err != nil { + return nil + } + } + + return exchangeClient +} + +func (cl *ExchangeClient) doInit(url *common.URL) error { + if cl.init { + return nil + } + if cl.client.Connect(url) != nil { + //retry for a while + time.Sleep(100 * time.Millisecond) + if cl.client.Connect(url) != nil { + logger.Errorf("Failed to connect server %+v " + url.Location) + return errors.New("Failed to connect server " + url.Location) + } + } + //FIXME atomic operation + cl.init = true + return nil +} + +// two way request +func (client *ExchangeClient) Request(invocation *protocol.Invocation, url *common.URL, timeout time.Duration, + result *protocol.RPCResult) error { + if er := client.doInit(url); er != nil { + return er + } + request := NewRequest("2.0.2") + request.Data = invocation + request.Event = false + request.TwoWay = true + + rsp := NewPendingResponse(request.ID) + rsp.response = NewResponse(request.ID, "2.0.2") + rsp.Reply = (*invocation).Reply() + AddPendingResponse(rsp) + + err := client.client.Request(request, timeout, rsp) + // request error + if err != nil { + result.Err = err + return err + } + if resultTmp, ok := rsp.response.Result.(*protocol.RPCResult); ok { + result.Rest = resultTmp.Rest + result.Attrs = resultTmp.Attrs + result.Err = resultTmp.Err + } + return nil +} + +// async two way request +func (client *ExchangeClient) AsyncRequest(invocation *protocol.Invocation, url *common.URL, timeout time.Duration, + callback common.AsyncCallback, result *protocol.RPCResult) error { + if er := client.doInit(url); er != nil { + return er + } + request := NewRequest("2.0.2") + request.Data = invocation + request.Event = false + request.TwoWay = true + + rsp := NewPendingResponse(request.ID) + rsp.response = NewResponse(request.ID, "2.0.2") + rsp.Callback = callback + rsp.Reply = (*invocation).Reply() + AddPendingResponse(rsp) + + err := client.client.Request(request, timeout, rsp) + if err != nil { + result.Err = err + return err + } + result.Rest = rsp.response + return nil +} + +// oneway request +func (client *ExchangeClient) Send(invocation *protocol.Invocation, url *common.URL, timeout time.Duration) error { + if er := client.doInit(url); er != nil { + return er + } + request := NewRequest("2.0.2") + request.Data = invocation + request.Event = false + request.TwoWay = false + + rsp := NewPendingResponse(request.ID) + rsp.response = NewResponse(request.ID, "2.0.2") + + err := client.client.Request(request, timeout, rsp) + if err != nil { + return err + } + return nil +} + +// close client +func (client *ExchangeClient) Close() { + client.client.Close() + // for reinit client + client.init = false +} + +// IsAvailable to check if the underlying network client is available yet. +func (client *ExchangeClient) IsAvailable() bool { + return client.client.IsAvailable() +} diff --git a/remoting/exchange_server.go b/remoting/exchange_server.go new file mode 100644 index 0000000000000000000000000000000000000000..a8d7c73f305a42e0402861817e5dbe2abfcfdd01 --- /dev/null +++ b/remoting/exchange_server.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 remoting + +import ( + "github.com/apache/dubbo-go/common" +) + +// It is interface of server for network communication. +// If you use getty as network communication, you should define GettyServer that implements this interface. +type Server interface { + //invoke once for connection + Start() + //it is for destroy + Stop() +} + +// This is abstraction level. it is like facade. +type ExchangeServer struct { + Server Server + Url *common.URL +} + +// Create ExchangeServer +func NewExchangeServer(url *common.URL, server Server) *ExchangeServer { + exchangServer := &ExchangeServer{ + Server: server, + Url: url, + } + return exchangServer +} + +// start server +func (server *ExchangeServer) Start() { + server.Server.Start() +} + +// stop server +func (server *ExchangeServer) Stop() { + server.Server.Stop() +} diff --git a/protocol/dubbo/config.go b/remoting/getty/config.go similarity index 80% rename from protocol/dubbo/config.go rename to remoting/getty/config.go index b47ec1cc3422dcbcac921f08888c7a777e72e246..b6aa08206a1497b2bac5904eef7b86bde11a61ac 100644 --- a/protocol/dubbo/config.go +++ b/remoting/getty/config.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package dubbo +package getty import ( "time" @@ -30,7 +30,7 @@ import ( ) type ( - // GettySessionParam is session configuration for getty. + // 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"` @@ -52,6 +52,16 @@ type ( // ServerConfig holds supported types by the multiconfig package ServerConfig struct { + SSLEnabled bool + + // heartbeat + HeartbeatPeriod string `default:"60s" yaml:"heartbeat_period" json:"heartbeat_period,omitempty"` + heartbeatPeriod time.Duration + + // heartbeat timeout + HeartbeatTimeout string `default:"5s" yaml:"heartbeat_timeout" json:"heartbeat_timeout,omitempty"` + heartbeatTimeout time.Duration + // session SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` sessionTimeout time.Duration @@ -74,9 +84,13 @@ type ( ConnectionNum int `default:"16" yaml:"connection_number" json:"connection_number,omitempty"` // heartbeat - HeartbeatPeriod string `default:"15s" yaml:"heartbeat_period" json:"heartbeat_period,omitempty"` + HeartbeatPeriod string `default:"60s" yaml:"heartbeat_period" json:"heartbeat_period,omitempty"` heartbeatPeriod time.Duration + // heartbeat timeout + HeartbeatTimeout string `default:"5s" yaml:"heartbeat_timeout" json:"heartbeat_timeout,omitempty"` + heartbeatTimeout time.Duration + // session SessionTimeout string `default:"60s" yaml:"session_timeout" json:"session_timeout,omitempty"` sessionTimeout time.Duration @@ -95,7 +109,7 @@ type ( } ) -// GetDefaultClientConfig gets client default configuration. +// GetDefaultClientConfig gets client default configuration func GetDefaultClientConfig() ClientConfig { return ClientConfig{ ReconnectInterval: 0, @@ -123,7 +137,7 @@ func GetDefaultClientConfig() ClientConfig { }} } -// GetDefaultServerConfig gets server default configuration. +// GetDefaultServerConfig gets server default configuration func GetDefaultServerConfig() ServerConfig { return ServerConfig{ SessionTimeout: "180s", @@ -148,7 +162,7 @@ func GetDefaultServerConfig() ServerConfig { } } -// CheckValidity confirm getty sessian params. +// CheckValidity confirm getty sessian params func (c *GettySessionParam) CheckValidity() error { var err error @@ -186,6 +200,12 @@ func (c *ClientConfig) CheckValidity() error { c.HeartbeatPeriod, time.Duration(config.MaxWheelTimeSpan)) } + if len(c.HeartbeatTimeout) == 0 { + c.heartbeatTimeout = 60 * time.Second + } else if c.heartbeatTimeout, err = time.ParseDuration(c.HeartbeatTimeout); err != nil { + return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatTimeout{%#v})", c.HeartbeatTimeout) + } + if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } @@ -193,10 +213,27 @@ func (c *ClientConfig) CheckValidity() error { return perrors.WithStack(c.GettySessionParam.CheckValidity()) } -// CheckValidity confirm server params. +// CheckValidity confirm server params func (c *ServerConfig) CheckValidity() error { var err error + if len(c.HeartbeatPeriod) == 0 { + c.heartbeatPeriod = 60 * time.Second + } else if c.heartbeatPeriod, err = time.ParseDuration(c.HeartbeatPeriod); err != nil { + return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatPeroid{%#v})", c.HeartbeatPeriod) + } + + if c.heartbeatPeriod >= time.Duration(config.MaxWheelTimeSpan) { + return perrors.WithMessagef(err, "heartbeat_period %s should be less than %s", + c.HeartbeatPeriod, time.Duration(config.MaxWheelTimeSpan)) + } + + if len(c.HeartbeatTimeout) == 0 { + c.heartbeatTimeout = 60 * time.Second + } else if c.heartbeatTimeout, err = time.ParseDuration(c.HeartbeatTimeout); err != nil { + return perrors.WithMessagef(err, "time.ParseDuration(HeartbeatTimeout{%#v})", c.HeartbeatTimeout) + } + if c.sessionTimeout, err = time.ParseDuration(c.SessionTimeout); err != nil { return perrors.WithMessagef(err, "time.ParseDuration(SessionTimeout{%#v})", c.SessionTimeout) } diff --git a/remoting/getty/dubbo_codec_for_test.go b/remoting/getty/dubbo_codec_for_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fca5da89643adee2f5e6c1e8dca8ec3da4c987e4 --- /dev/null +++ b/remoting/getty/dubbo_codec_for_test.go @@ -0,0 +1,276 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package getty + +// copy from dubbo/dubbo_codec.go . +// it is used to unit test. +import ( + "bytes" + "strconv" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/dubbo/impl" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +func init() { + codec := &DubboTestCodec{} + remoting.RegistryCodec("dubbo", codec) +} + +type DubboTestCodec struct { +} + +// encode request for transport +func (c *DubboTestCodec) EncodeRequest(request *remoting.Request) (*bytes.Buffer, error) { + if request.Event { + return c.encodeHeartbeartReqeust(request) + } + + invoc, ok := request.Data.(*invocation.RPCInvocation) + if !ok { + return nil, perrors.Errorf("encode request failed for parameter type :%+v", request) + } + tmpInvocation := invoc + + svc := impl.Service{} + svc.Path = tmpInvocation.AttachmentsByKey(constant.PATH_KEY, "") + svc.Interface = tmpInvocation.AttachmentsByKey(constant.INTERFACE_KEY, "") + svc.Version = tmpInvocation.AttachmentsByKey(constant.VERSION_KEY, "") + svc.Group = tmpInvocation.AttachmentsByKey(constant.GROUP_KEY, "") + svc.Method = tmpInvocation.MethodName() + timeout, err := strconv.Atoi(tmpInvocation.AttachmentsByKey(constant.TIMEOUT_KEY, strconv.Itoa(constant.DEFAULT_REMOTING_TIMEOUT))) + if err != nil { + // it will be wrapped in readwrite.Write . + return nil, perrors.WithStack(err) + } + svc.Timeout = time.Duration(timeout) + + header := impl.DubboHeader{} + serialization := tmpInvocation.AttachmentsByKey(constant.SERIALIZATION_KEY, constant.HESSIAN2_SERIALIZATION) + if serialization == constant.PROTOBUF_SERIALIZATION { + header.SerialID = constant.S_Proto + } else { + header.SerialID = constant.S_Hessian2 + } + header.ID = request.ID + if request.TwoWay { + header.Type = impl.PackageRequest_TwoWay + } else { + header.Type = impl.PackageRequest + } + + pkg := &impl.DubboPackage{ + Header: header, + Service: svc, + Body: impl.NewRequestPayload(tmpInvocation.Arguments(), tmpInvocation.Attachments()), + Err: nil, + Codec: impl.NewDubboCodec(nil), + } + + if err := impl.LoadSerializer(pkg); err != nil { + return nil, perrors.WithStack(err) + } + + return pkg.Marshal() +} + +// encode heartbeart request +func (c *DubboTestCodec) encodeHeartbeartReqeust(request *remoting.Request) (*bytes.Buffer, error) { + header := impl.DubboHeader{ + Type: impl.PackageHeartbeat, + SerialID: constant.S_Hessian2, + ID: request.ID, + } + + pkg := &impl.DubboPackage{ + Header: header, + Service: impl.Service{}, + Body: impl.NewRequestPayload([]interface{}{}, nil), + Err: nil, + Codec: impl.NewDubboCodec(nil), + } + + if err := impl.LoadSerializer(pkg); err != nil { + return nil, err + } + return pkg.Marshal() +} + +// encode response +func (c *DubboTestCodec) EncodeResponse(response *remoting.Response) (*bytes.Buffer, error) { + var ptype = impl.PackageResponse + if response.IsHeartbeat() { + ptype = impl.PackageHeartbeat + } + resp := &impl.DubboPackage{ + Header: impl.DubboHeader{ + SerialID: response.SerialID, + Type: ptype, + ID: response.ID, + ResponseStatus: response.Status, + }, + } + if !response.IsHeartbeat() { + resp.Body = &impl.ResponsePayload{ + RspObj: response.Result.(protocol.RPCResult).Rest, + Exception: response.Result.(protocol.RPCResult).Err, + Attachments: response.Result.(protocol.RPCResult).Attrs, + } + } + + codec := impl.NewDubboCodec(nil) + + pkg, err := codec.Encode(*resp) + if err != nil { + return nil, perrors.WithStack(err) + } + + return bytes.NewBuffer(pkg), nil +} + +// Decode data, including request and response. +func (c *DubboTestCodec) Decode(data []byte) (remoting.DecodeResult, int, error) { + if c.isRequest(data) { + req, len, err := c.decodeRequest(data) + if err != nil { + return remoting.DecodeResult{}, len, perrors.WithStack(err) + } + return remoting.DecodeResult{IsRequest: true, Result: req}, len, perrors.WithStack(err) + } else { + resp, len, err := c.decodeResponse(data) + if err != nil { + return remoting.DecodeResult{}, len, perrors.WithStack(err) + } + return remoting.DecodeResult{IsRequest: false, Result: resp}, len, perrors.WithStack(err) + } +} + +func (c *DubboTestCodec) isRequest(data []byte) bool { + if data[2]&byte(0x80) == 0x00 { + return false + } + return true +} + +// decode request +func (c *DubboTestCodec) decodeRequest(data []byte) (*remoting.Request, int, error) { + var request *remoting.Request = nil + buf := bytes.NewBuffer(data) + pkg := impl.NewDubboPackage(buf) + pkg.SetBody(make([]interface{}, 7)) + err := pkg.Unmarshal() + if err != nil { + originErr := perrors.Cause(err) + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + //FIXME + return nil, 0, originErr + } + return request, 0, perrors.WithStack(err) + } + request = &remoting.Request{ + ID: pkg.Header.ID, + SerialID: pkg.Header.SerialID, + TwoWay: pkg.Header.Type&impl.PackageRequest_TwoWay != 0x00, + Event: pkg.Header.Type&impl.PackageHeartbeat != 0x00, + } + if (pkg.Header.Type & impl.PackageHeartbeat) == 0x00 { + // convert params of request + req := pkg.Body.(map[string]interface{}) + + //invocation := request.Data.(*invocation.RPCInvocation) + var methodName string + var args []interface{} + attachments := make(map[string]interface{}) + if req[impl.DubboVersionKey] != nil { + //dubbo version + request.Version = req[impl.DubboVersionKey].(string) + } + //path + attachments[constant.PATH_KEY] = pkg.Service.Path + //version + attachments[constant.VERSION_KEY] = pkg.Service.Version + //method + methodName = pkg.Service.Method + args = req[impl.ArgsKey].([]interface{}) + attachments = req[impl.AttachmentsKey].(map[string]interface{}) + invoc := invocation.NewRPCInvocationWithOptions(invocation.WithAttachments(attachments), + invocation.WithArguments(args), invocation.WithMethodName(methodName)) + request.Data = invoc + + } + return request, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} + +// decode response +func (c *DubboTestCodec) decodeResponse(data []byte) (*remoting.Response, int, error) { + buf := bytes.NewBuffer(data) + pkg := impl.NewDubboPackage(buf) + response := &remoting.Response{} + err := pkg.Unmarshal() + if err != nil { + originErr := perrors.Cause(err) + // if the data is very big, so the receive need much times. + if originErr == hessian.ErrHeaderNotEnough || originErr == hessian.ErrBodyNotEnough { + return nil, 0, originErr + } + return nil, 0, perrors.WithStack(err) + } + response = &remoting.Response{ + ID: pkg.Header.ID, + //Version: pkg.Header., + SerialID: pkg.Header.SerialID, + Status: pkg.Header.ResponseStatus, + Event: (pkg.Header.Type & impl.PackageHeartbeat) != 0, + } + var error error + if pkg.Header.Type&impl.PackageHeartbeat != 0x00 { + if pkg.Header.Type&impl.PackageResponse != 0x00 { + if pkg.Err != nil { + error = pkg.Err + } + } else { + response.Status = hessian.Response_OK + //reply(session, p, hessian.PackageHeartbeat) + } + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, error + } + rpcResult := &protocol.RPCResult{} + response.Result = rpcResult + if pkg.Header.Type&impl.PackageRequest == 0x00 { + if pkg.Err != nil { + rpcResult.Err = pkg.Err + } else if pkg.Body.(*impl.ResponsePayload).Exception != nil { + rpcResult.Err = pkg.Body.(*impl.ResponsePayload).Exception + response.Error = rpcResult.Err + } + rpcResult.Attrs = pkg.Body.(*impl.ResponsePayload).Attachments + rpcResult.Rest = pkg.Body.(*impl.ResponsePayload).RspObj + } + + return response, hessian.HEADER_LENGTH + pkg.Header.BodyLen, nil +} diff --git a/remoting/getty/getty_client.go b/remoting/getty/getty_client.go new file mode 100644 index 0000000000000000000000000000000000000000..e432ca55a9bfb32575fad183f93438b8cb7801fb --- /dev/null +++ b/remoting/getty/getty_client.go @@ -0,0 +1,222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "math/rand" + "time" +) + +import ( + "github.com/apache/dubbo-getty" + gxsync "github.com/dubbogo/gost/sync" + perrors "github.com/pkg/errors" + "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" + "github.com/apache/dubbo-go/remoting" +) + +var ( + errInvalidCodecType = perrors.New("illegal CodecType") + errInvalidAddress = perrors.New("remote address invalid or empty") + errSessionNotExist = perrors.New("session not exist") + errClientClosed = perrors.New("client closed") + errClientReadTimeout = perrors.New("client read timeout") + + clientConf *ClientConfig + clientGrpool *gxsync.TaskPool +) + +// it is init client for single protocol. +func initClient(protocol string) { + if protocol == "" { + return + } + + // load clientconfig from consumer_config + // default use dubbo + consumerConfig := config.GetConsumerConfig() + if consumerConfig.ApplicationConfig == nil { + return + } + protocolConf := config.GetConsumerConfig().ProtocolConf + defaultClientConfig := GetDefaultClientConfig() + if protocolConf == nil { + logger.Info("protocol_conf default use dubbo config") + } else { + dubboConf := protocolConf.(map[interface{}]interface{})[protocol] + if dubboConf == nil { + logger.Warnf("dubboConf is nil") + return + } + dubboConfByte, err := yaml.Marshal(dubboConf) + if err != nil { + panic(err) + } + err = yaml.Unmarshal(dubboConfByte, &defaultClientConfig) + if err != nil { + panic(err) + } + } + clientConf = &defaultClientConfig + if err := clientConf.CheckValidity(); err != nil { + logger.Warnf("[CheckValidity] error: %v", err) + return + } + setClientGrpool() + + rand.Seed(time.Now().UnixNano()) +} + +// Config ClientConf +func SetClientConf(c ClientConfig) { + clientConf = &c + err := clientConf.CheckValidity() + if err != nil { + logger.Warnf("[ClientConfig CheckValidity] error: %v", err) + return + } + setClientGrpool() +} + +func setClientGrpool() { + if clientConf.GrPoolSize > 1 { + clientGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(clientConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(clientConf.QueueLen), + gxsync.WithTaskPoolTaskQueueNumber(clientConf.QueueNumber)) + } +} + +// Options : param config +type Options struct { + // connect timeout + // remove request timeout, it will be calulate for every request + ConnectTimeout time.Duration + // request timeout + RequestTimeout time.Duration +} + +// Client : some configuration for network communication. +type Client struct { + addr string + opts Options + conf ClientConfig + pool *gettyRPCClientPool + codec remoting.Codec + ExchangeClient *remoting.ExchangeClient +} + +// create client +func NewClient(opt Options) *Client { + switch { + case opt.ConnectTimeout == 0: + opt.ConnectTimeout = 3 * time.Second + fallthrough + case opt.RequestTimeout == 0: + opt.RequestTimeout = 3 * time.Second + } + + c := &Client{ + opts: opt, + } + return c +} + +func (c *Client) SetExchangeClient(client *remoting.ExchangeClient) { + c.ExchangeClient = client +} + +// init client and try to connection. +func (c *Client) Connect(url *common.URL) error { + initClient(url.Protocol) + c.conf = *clientConf + // new client + c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) + c.pool.sslEnabled = url.GetParamBool(constant.SSL_ENABLED_KEY, false) + + // codec + c.codec = remoting.GetCodec(url.Protocol) + c.addr = url.Location + _, _, err := c.selectSession(c.addr) + if err != nil { + logger.Errorf("try to connect server %v failed for : %v", url.Location, err) + } + return err +} + +// close network connection +func (c *Client) Close() { + if c.pool != nil { + c.pool.close() + } + c.pool = nil +} + +// send request +func (c *Client) Request(request *remoting.Request, timeout time.Duration, response *remoting.PendingResponse) error { + _, session, err := c.selectSession(c.addr) + if err != nil { + return perrors.WithStack(err) + } + if session == nil { + return errSessionNotExist + } + + if err = c.transfer(session, request, timeout); err != nil { + return perrors.WithStack(err) + } + + if !request.TwoWay || response.Callback != nil { + return nil + } + + select { + case <-getty.GetTimeWheel().After(timeout): + return perrors.WithStack(errClientReadTimeout) + case <-response.Done: + err = response.Err + } + + return perrors.WithStack(err) +} + +// isAvailable returns true if the connection is available, or it can be re-established. +func (c *Client) IsAvailable() bool { + client, _, err := c.selectSession(c.addr) + return err == nil && + // defensive check + client != nil +} + +func (c *Client) selectSession(addr string) (*gettyRPCClient, getty.Session, error) { + rpcClient, err := c.pool.getGettyRpcClient(addr) + if err != nil { + return nil, nil, perrors.WithStack(err) + } + return rpcClient, rpcClient.selectSession(), nil +} + +func (c *Client) transfer(session getty.Session, request *remoting.Request, timeout time.Duration) error { + err := session.WritePkg(request, timeout) + return perrors.WithStack(err) +} diff --git a/remoting/getty/getty_client_test.go b/remoting/getty/getty_client_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0b18e973cd2ea7a3f6aae59e822aaf68ee983331 --- /dev/null +++ b/remoting/getty/getty_client_test.go @@ -0,0 +1,493 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "bytes" + "context" + "reflect" + "sync" + "testing" + "time" +) + +import ( + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" + "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/proxy/proxy_factory" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +func TestRunSuite(t *testing.T) { + svr, url := InitTest(t) + client := getClient(url) + testRequestOneWay(t, svr, url, client) + testClient_Call(t, svr, url, client) + testClient_AsyncCall(t, svr, url, client) + svr.Stop() +} + +func testRequestOneWay(t *testing.T, svr *Server, url *common.URL, client *Client) { + + request := remoting.NewRequest("2.0.2") + up := &UserProvider{} + invocation := createInvocation("GetUser", nil, nil, []interface{}{[]interface{}{"1", "username"}, up}, + []reflect.Value{reflect.ValueOf([]interface{}{"1", "username"}), reflect.ValueOf(up)}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = false + //user := &User{} + err := client.Request(request, 3*time.Second, nil) + assert.NoError(t, err) +} + +func createInvocation(methodName string, callback interface{}, reply interface{}, arguments []interface{}, + parameterValues []reflect.Value) *invocation.RPCInvocation { + return invocation.NewRPCInvocationWithOptions(invocation.WithMethodName(methodName), + invocation.WithArguments(arguments), invocation.WithReply(reply), + invocation.WithCallBack(callback), invocation.WithParameterValues(parameterValues)) +} + +func setAttachment(invocation *invocation.RPCInvocation, attachments map[string]string) { + for key, value := range attachments { + invocation.SetAttachments(key, value) + } +} + +func getClient(url *common.URL) *Client { + client := NewClient(Options{ + ConnectTimeout: config.GetConsumerConfig().ConnectTimeout, + }) + + exchangeClient := remoting.NewExchangeClient(url, client, 5*time.Second, false) + client.SetExchangeClient(exchangeClient) + client.Connect(url) + return client +} + +func testClient_Call(t *testing.T, svr *Server, url *common.URL, c *Client) { + c.pool = newGettyRPCClientConnPool(c, clientConf.PoolSize, time.Duration(int(time.Second)*clientConf.PoolTTL)) + + testGetBigPkg(t, c) + testGetUser(t, c) + testGetUser0(t, c) + testGetUser1(t, c) + testGetUser2(t, c) + testGetUser3(t, c) + testGetUser4(t, c) + testGetUser5(t, c) + testGetUser6(t, c) + testGetUser61(t, c) +} + +func testGetBigPkg(t *testing.T, c *Client) { + user := &User{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetBigPkg", nil, nil, []interface{}{[]interface{}{nil}, user}, + []reflect.Value{reflect.ValueOf([]interface{}{nil}), reflect.ValueOf(user)}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err := c.Request(request, 8*time.Second, pendingResponse) + assert.NoError(t, err) + assert.NotEqual(t, "", user.Id) + assert.NotEqual(t, "", user.Name) +} + +func testGetUser(t *testing.T, c *Client) { + user := &User{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser", nil, nil, []interface{}{"1", "username"}, + []reflect.Value{reflect.ValueOf("1"), reflect.ValueOf("username")}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err := c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, User{Id: "1", Name: "username"}, *user) +} + +func testGetUser0(t *testing.T, c *Client) { + var ( + user *User + err error + ) + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser0", nil, nil, []interface{}{"1", nil, "username"}, + []reflect.Value{reflect.ValueOf("1"), reflect.ValueOf(nil), reflect.ValueOf("username")}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + rsp := remoting.NewPendingResponse(request.ID) + rsp.SetResponse(remoting.NewResponse(request.ID, "2.0.2")) + remoting.AddPendingResponse(rsp) + rsp.Reply = user + err = c.Request(request, 3*time.Second, rsp) + assert.NoError(t, err) + assert.Equal(t, User{Id: "1", Name: "username"}, *user) +} + +func testGetUser1(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser1", nil, nil, []interface{}{}, + []reflect.Value{}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user := &User{} + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) +} + +func testGetUser2(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser2", nil, nil, []interface{}{}, + []reflect.Value{}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.EqualError(t, err, "error") +} + +func testGetUser3(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser3", nil, nil, []interface{}{}, + []reflect.Value{}) + attachment := map[string]string{ + INTERFACE_KEY: "com.ikurento.user.UserProvider", + } + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user2 := []interface{}{} + pendingResponse.Reply = &user2 + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) +} + +func testGetUser4(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser4", []interface{}{[]interface{}{"1", "username"}}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user2 := []interface{}{} + pendingResponse.Reply = &user2 + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, &User{Id: "1", Name: "username"}, user2[0]) +} + +func testGetUser5(t *testing.T, c *Client) { + var ( + err error + ) + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser5", []interface{}{map[interface{}]interface{}{"id": "1", "name": "username"}}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + user3 := map[interface{}]interface{}{} + pendingResponse.Reply = &user3 + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.NotNil(t, user3) + assert.Equal(t, &User{Id: "1", Name: "username"}, user3["key"]) +} + +func testGetUser6(t *testing.T, c *Client) { + var ( + user *User + err error + ) + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser6", []interface{}{0}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, User{Id: "", Name: ""}, *user) +} + +func testGetUser61(t *testing.T, c *Client) { + var ( + user *User + err error + ) + user = &User{} + request := remoting.NewRequest("2.0.2") + invocation := invocation.NewRPCInvocation("GetUser6", []interface{}{1}, nil) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + pendingResponse := remoting.NewPendingResponse(request.ID) + pendingResponse.Reply = user + remoting.AddPendingResponse(pendingResponse) + err = c.Request(request, 3*time.Second, pendingResponse) + assert.NoError(t, err) + assert.Equal(t, User{Id: "1", Name: ""}, *user) +} + +func testClient_AsyncCall(t *testing.T, svr *Server, url *common.URL, client *Client) { + user := &User{} + lock := sync.Mutex{} + request := remoting.NewRequest("2.0.2") + invocation := createInvocation("GetUser0", nil, nil, []interface{}{"4", nil, "username"}, + []reflect.Value{reflect.ValueOf("4"), reflect.ValueOf(nil), reflect.ValueOf("username")}) + attachment := map[string]string{INTERFACE_KEY: "com.ikurento.user.UserProvider"} + setAttachment(invocation, attachment) + request.Data = invocation + request.Event = false + request.TwoWay = true + rsp := remoting.NewPendingResponse(request.ID) + rsp.SetResponse(remoting.NewResponse(request.ID, "2.0.2")) + remoting.AddPendingResponse(rsp) + rsp.Reply = user + rsp.Callback = func(response common.CallbackResponse) { + r := response.(remoting.AsyncCallbackResponse) + rst := *r.Reply.(*remoting.Response).Result.(*protocol.RPCResult) + assert.Equal(t, User{Id: "4", Name: "username"}, *(rst.Rest.(*User))) + lock.Unlock() + } + lock.Lock() + err := client.Request(request, 3*time.Second, rsp) + assert.NoError(t, err) + assert.Equal(t, User{}, *user) + time.Sleep(1 * time.Second) +} + +func InitTest(t *testing.T) (*Server, *common.URL) { + + hessian.RegisterPOJO(&User{}) + remoting.RegistryCodec("dubbo", &DubboTestCodec{}) + + 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) + + // config + SetClientConf(ClientConfig{ + ConnectionNum: 2, + HeartbeatPeriod: "5s", + SessionTimeout: "20s", + PoolTTL: 600, + PoolSize: 64, + GettySessionParam: GettySessionParam{ + CompressEncoding: false, + TcpNoDelay: true, + TcpKeepAlive: true, + KeepAlivePeriod: "120s", + TcpRBufSize: 262144, + TcpWBufSize: 65536, + PkgWQSize: 512, + TcpReadTimeout: "4s", + TcpWriteTimeout: "5s", + WaitTimeout: "1s", + MaxMsgLen: 10240000000, + SessionName: "client", + }, + }) + assert.NoError(t, clientConf.CheckValidity()) + SetServerConfig(ServerConfig{ + SessionNumber: 700, + SessionTimeout: "20s", + GettySessionParam: 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", + }}) + assert.NoError(t, srvConf.CheckValidity()) + + url, err := common.NewURL("dubbo://127.0.0.1:20060/com.ikurento.user.UserProvider?anyhost=true&" + + "application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&" + + "environment=dev&interface=com.ikurento.user.UserProvider&ip=127.0.0.1&methods=GetUser%2C&" + + "module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&" + + "side=provider&timeout=3000×tamp=1556509797245&bean.name=UserProvider") + // init server + userProvider := &UserProvider{} + common.ServiceMap.Register("", url.Protocol, "", "0.0.1", userProvider) + invoker := &proxy_factory.ProxyInvoker{ + BaseInvoker: *protocol.NewBaseInvoker(url), + } + handler := func(invocation *invocation.RPCInvocation) protocol.RPCResult { + //result := protocol.RPCResult{} + r := invoker.Invoke(context.Background(), invocation) + result := protocol.RPCResult{ + Err: r.Error(), + Rest: r.Result(), + Attrs: r.Attachments(), + } + return result + } + server := NewServer(url, handler) + server.Start() + + time.Sleep(time.Second * 2) + + return server, url +} + +////////////////////////////////// +// provider +////////////////////////////////// + +type ( + User struct { + Id string `json:"id"` + Name string `json:"name"` + } + + UserProvider struct { + user map[string]User + } +) + +// size:4801228 +func (u *UserProvider) GetBigPkg(ctx context.Context, req []interface{}, rsp *User) error { + argBuf := new(bytes.Buffer) + for i := 0; i < 400; i++ { + argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") + argBuf.WriteString("鍑婚紦鍏堕晽锛岃笂璺冪敤鍏点€傚湡鍥藉煄婕曪紝鎴戠嫭鍗楄銆備粠瀛欏瓙浠诧紝骞抽檲涓庡畫銆備笉鎴戜互褰掞紝蹇у績鏈夊俊銆傜埌灞呯埌澶勶紵鐖颁抚鍏堕┈锛熶簬浠ユ眰涔嬶紵浜庢灄涔嬩笅銆傛鐢熷闃旓紝涓庡瓙鎴愯銆傛墽瀛愪箣鎵嬶紝涓庡瓙鍋曡€併€備簬鍡熼様鍏紝涓嶆垜娲诲叜銆備簬鍡熸吹鍏紝涓嶆垜淇″叜銆�") + } + rsp.Id = argBuf.String() + rsp.Name = argBuf.String() + return nil +} + +func (u *UserProvider) GetUser(ctx context.Context, req []interface{}, rsp *User) error { + rsp.Id = req[0].(string) + rsp.Name = req[1].(string) + return nil +} + +func (u *UserProvider) GetUser0(id string, k *User, name string) (User, error) { + return User{Id: id, Name: name}, nil +} + +func (u *UserProvider) GetUser1() error { + return nil +} + +func (u *UserProvider) GetUser2() error { + return perrors.New("error") +} + +func (u *UserProvider) GetUser3(rsp *[]interface{}) error { + *rsp = append(*rsp, User{Id: "1", Name: "username"}) + return nil +} + +func (u *UserProvider) GetUser4(ctx context.Context, req []interface{}) ([]interface{}, error) { + + return []interface{}{User{Id: req[0].([]interface{})[0].(string), Name: req[0].([]interface{})[1].(string)}}, nil +} + +func (u *UserProvider) GetUser5(ctx context.Context, req []interface{}) (map[interface{}]interface{}, error) { + return map[interface{}]interface{}{"key": User{Id: req[0].(map[interface{}]interface{})["id"].(string), Name: req[0].(map[interface{}]interface{})["name"].(string)}}, nil +} + +func (u *UserProvider) GetUser6(id int64) (*User, error) { + if id == 0 { + return nil, nil + } + return &User{Id: "1"}, nil +} + +func (u *UserProvider) Reference() string { + return "UserProvider" +} + +func (u User) JavaClassName() string { + return "com.ikurento.user.User" +} diff --git a/protocol/dubbo/server.go b/remoting/getty/getty_server.go similarity index 60% rename from protocol/dubbo/server.go rename to remoting/getty/getty_server.go index 4ad4796c5409e39984af3ff336c2556507d15d93..620a01d83b11e4dd4db0dc25bc8f5759d3a64579 100644 --- a/protocol/dubbo/server.go +++ b/remoting/getty/getty_server.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package dubbo +package getty import ( "crypto/tls" @@ -25,7 +25,8 @@ import ( import ( "github.com/apache/dubbo-getty" - "github.com/dubbogo/gost/sync" + gxsync "github.com/dubbogo/gost/sync" + perrors "github.com/pkg/errors" "gopkg.in/yaml.v2" ) @@ -34,6 +35,9 @@ import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/common/logger" "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" ) var ( @@ -41,8 +45,7 @@ var ( srvGrpool *gxsync.TaskPool ) -func init() { - +func initServer(protocol string) { // load clientconfig from provider_config // default use dubbo providerConfig := config.GetProviderConfig() @@ -54,7 +57,7 @@ func init() { if protocolConf == nil { logger.Info("protocol_conf default use dubbo config") } else { - dubboConf := protocolConf.(map[interface{}]interface{})[DUBBO] + dubboConf := protocolConf.(map[interface{}]interface{})[protocol] if dubboConf == nil { logger.Warnf("dubboConf is nil") return @@ -73,7 +76,7 @@ func init() { if err := srvConf.CheckValidity(); err != nil { panic(err) } - setServerGrpool() + SetServerGrpool() } // SetServerConfig set dubbo server config. @@ -84,36 +87,50 @@ func SetServerConfig(s ServerConfig) { logger.Warnf("[ServerConfig CheckValidity] error: %v", err) return } - setServerGrpool() + SetServerGrpool() } -// GetServerConfig get dubbo server config. +// GetServerConfig get getty server config. func GetServerConfig() ServerConfig { return *srvConf } -func setServerGrpool() { +// SetServerGrpool set getty server GrPool +func SetServerGrpool() { if srvConf.GrPoolSize > 1 { - srvGrpool = gxsync.NewTaskPool(gxsync.WithTaskPoolTaskPoolSize(srvConf.GrPoolSize), gxsync.WithTaskPoolTaskQueueLength(srvConf.QueueLen), - gxsync.WithTaskPoolTaskQueueNumber(srvConf.QueueNumber)) + srvGrpool = gxsync.NewTaskPool( + gxsync.WithTaskPoolTaskPoolSize(srvConf.GrPoolSize), + gxsync.WithTaskPoolTaskQueueLength(srvConf.QueueLen), + gxsync.WithTaskPoolTaskQueueNumber(srvConf.QueueNumber), + ) } } -// Server is dubbo protocol server. +// Server define getty server type Server struct { - conf ServerConfig - tcpServer getty.Server - rpcHandler *RpcServerHandler + conf ServerConfig + addr string + codec remoting.Codec + tcpServer getty.Server + rpcHandler *RpcServerHandler + requestHandler func(*invocation.RPCInvocation) protocol.RPCResult } -// NewServer create a new Server. -func NewServer() *Server { +// NewServer create a new Server +func NewServer(url *common.URL, handlers func(*invocation.RPCInvocation) protocol.RPCResult) *Server { + //init + initServer(url.Protocol) + + srvConf.SSLEnabled = url.GetParamBool(constant.SSL_ENABLED_KEY, false) s := &Server{ - conf: *srvConf, + conf: *srvConf, + addr: url.Location, + codec: remoting.GetCodec(url.Protocol), + requestHandler: handlers, } - s.rpcHandler = NewRpcServerHandler(s.conf.SessionNumber, s.conf.sessionTimeout) + s.rpcHandler = NewRpcServerHandler(s.conf.SessionNumber, s.conf.sessionTimeout, s) return s } @@ -122,6 +139,7 @@ func (s *Server) newSession(session getty.Session) error { var ( ok bool tcpConn *net.TCPConn + err error ) conf := s.conf @@ -131,12 +149,12 @@ func (s *Server) newSession(session getty.Session) error { if _, ok = session.Conn().(*tls.Conn); ok { session.SetName(conf.GettySessionParam.SessionName) session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) - session.SetPkgHandler(rpcServerPkgHandler) + session.SetPkgHandler(NewRpcServerPackageHandler(s)) session.SetEventListener(s.rpcHandler) session.SetWQLen(conf.GettySessionParam.PkgWQSize) session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) - session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) + session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6)) session.SetWaitTime(conf.GettySessionParam.waitTimeout) logger.Debugf("server accepts new session:%s\n", session.Stat()) session.SetTaskPool(srvGrpool) @@ -146,57 +164,69 @@ func (s *Server) newSession(session getty.Session) error { panic(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection\n", session.Stat(), session.Conn())) } - tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay) - tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive) - if conf.GettySessionParam.TcpKeepAlive { - tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.keepAlivePeriod) + if _, ok = session.Conn().(*tls.Conn); !ok { + if tcpConn, ok = session.Conn().(*net.TCPConn); !ok { + return perrors.New(fmt.Sprintf("%s, session.conn{%#v} is not tcp connection", session.Stat(), session.Conn())) + } + + if err = tcpConn.SetNoDelay(conf.GettySessionParam.TcpNoDelay); err != nil { + return err + } + if err = tcpConn.SetKeepAlive(conf.GettySessionParam.TcpKeepAlive); err != nil { + return err + } + if conf.GettySessionParam.TcpKeepAlive { + if err = tcpConn.SetKeepAlivePeriod(conf.GettySessionParam.keepAlivePeriod); err != nil { + return err + } + } + if err = tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize); err != nil { + return err + } + if err = tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize); err != nil { + return err + } } - tcpConn.SetReadBuffer(conf.GettySessionParam.TcpRBufSize) - tcpConn.SetWriteBuffer(conf.GettySessionParam.TcpWBufSize) session.SetName(conf.GettySessionParam.SessionName) session.SetMaxMsgLen(conf.GettySessionParam.MaxMsgLen) - session.SetPkgHandler(rpcServerPkgHandler) + session.SetPkgHandler(NewRpcServerPackageHandler(s)) session.SetEventListener(s.rpcHandler) session.SetWQLen(conf.GettySessionParam.PkgWQSize) session.SetReadTimeout(conf.GettySessionParam.tcpReadTimeout) session.SetWriteTimeout(conf.GettySessionParam.tcpWriteTimeout) - session.SetCronPeriod((int)(conf.sessionTimeout.Nanoseconds() / 1e6)) + session.SetCronPeriod((int)(conf.heartbeatPeriod.Nanoseconds() / 1e6)) session.SetWaitTime(conf.GettySessionParam.waitTimeout) - logger.Debugf("app accepts new session:%s\n", session.Stat()) - + logger.Debugf("server accepts new session: %s", session.Stat()) session.SetTaskPool(srvGrpool) - return nil } -// Start start dubbo server. -func (s *Server) Start(url common.URL) { +// Start dubbo server. +func (s *Server) Start() { var ( addr string tcpServer getty.Server ) - addr = url.Location - if url.GetParamBool(constant.SSL_ENABLED_KEY, false) { + addr = s.addr + if s.conf.SSLEnabled { tcpServer = getty.NewTCPServer( getty.WithLocalAddress(addr), - getty.WithServerSslEnabled(url.GetParamBool(constant.SSL_ENABLED_KEY, false)), + getty.WithServerSslEnabled(s.conf.SSLEnabled), getty.WithServerTlsConfigBuilder(config.GetServerTlsConfigBuilder()), ) - } else { tcpServer = getty.NewTCPServer( getty.WithLocalAddress(addr), ) } tcpServer.RunEventLoop(s.newSession) - logger.Debugf("s bind addr{%s} ok!", addr) + logger.Debugf("s bind addr{%s} ok!", s.addr) s.tcpServer = tcpServer - } -// Stop stop dubbo server. +// Stop dubbo server func (s *Server) Stop() { s.tcpServer.Close() } diff --git a/remoting/getty/listener.go b/remoting/getty/listener.go new file mode 100644 index 0000000000000000000000000000000000000000..b2f7790f2ffcab6832224e07219016cb46a3fa4d --- /dev/null +++ b/remoting/getty/listener.go @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "fmt" + "sync" + "sync/atomic" + "time" +) + +import ( + "github.com/apache/dubbo-getty" + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/protocol/invocation" + "github.com/apache/dubbo-go/remoting" +) + +// todo: WritePkg_Timeout will entry *.yml +const ( + // WritePkg_Timeout the timeout of write pkg + WritePkg_Timeout = 5 * time.Second +) + +var ( + errTooManySessions = perrors.New("too many sessions") + errHeartbeatReadTimeout = perrors.New("heartbeat read timeout") +) + +type rpcSession struct { + session getty.Session + reqNum int32 +} + +func (s *rpcSession) AddReqNum(num int32) { + atomic.AddInt32(&s.reqNum, num) +} + +func (s *rpcSession) GetReqNum() int32 { + return atomic.LoadInt32(&s.reqNum) +} + +// ////////////////////////////////////////// +// RpcClientHandler +// ////////////////////////////////////////// + +// nolint +type RpcClientHandler struct { + conn *gettyRPCClient + timeoutTimes int +} + +// nolint +func NewRpcClientHandler(client *gettyRPCClient) *RpcClientHandler { + return &RpcClientHandler{conn: client} +} + +// OnOpen call the getty client session opened, add the session to getty client session list +func (h *RpcClientHandler) OnOpen(session getty.Session) error { + h.conn.addSession(session) + return nil +} + +// OnError the getty client session has errored, so remove the session from the getty client session list +func (h *RpcClientHandler) OnError(session getty.Session, err error) { + logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) + h.conn.removeSession(session) +} + +// OnClose close the session, remove it from the getty session list +func (h *RpcClientHandler) OnClose(session getty.Session) { + logger.Infof("session{%s} is closing......", session.Stat()) + h.conn.removeSession(session) +} + +// OnMessage get response from getty server, and update the session to the getty client session list +func (h *RpcClientHandler) OnMessage(session getty.Session, pkg interface{}) { + result, ok := pkg.(remoting.DecodeResult) + if !ok { + logger.Errorf("illegal package") + return + } + // get heartbeart request from server + if result.IsRequest { + req := result.Result.(*remoting.Request) + if req.Event { + logger.Debugf("get rpc heartbeat request{%#v}", req) + resp := remoting.NewResponse(req.ID, req.Version) + resp.Status = hessian.Response_OK + resp.Event = req.Event + resp.SerialID = req.SerialID + resp.Version = "2.0.2" + reply(session, resp) + return + } + logger.Errorf("illegal request but not heartbeart. {%#v}", req) + return + } + h.timeoutTimes = 0 + p := result.Result.(*remoting.Response) + // get heartbeart + if p.Event { + logger.Debugf("get rpc heartbeat response{%#v}", p) + if p.Error != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", p.Error) + } + p.Handle() + return + } + + logger.Debugf("get rpc response{%#v}", p) + + h.conn.updateSession(session) + + p.Handle() +} + +// OnCron check the session health periodic. if the session's sessionTimeout has reached, just close the session +func (h *RpcClientHandler) OnCron(session getty.Session) { + rpcSession, err := h.conn.getClientRpcSession(session) + if err != nil { + logger.Errorf("client.getClientSession(session{%s}) = error{%v}", + session.Stat(), perrors.WithStack(err)) + return + } + 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) + h.conn.removeSession(session) // -> h.conn.close() -> h.conn.pool.remove(h.conn) + return + } + + heartbeatCallBack := func(err error) { + if err != nil { + logger.Warnf("failed to send heartbeat, error{%v}", err) + if h.timeoutTimes >= 3 { + h.conn.removeSession(session) + return + } + h.timeoutTimes++ + return + } + h.timeoutTimes = 0 + } + + if err := heartbeat(session, h.conn.pool.rpcClient.conf.heartbeatTimeout, heartbeatCallBack); err != nil { + logger.Warnf("failed to send heartbeat, error{%v}", err) + } +} + +// ////////////////////////////////////////// +// RpcServerHandler +// ////////////////////////////////////////// + +// nolint +type RpcServerHandler struct { + maxSessionNum int + sessionTimeout time.Duration + sessionMap map[getty.Session]*rpcSession + rwlock sync.RWMutex + server *Server + timeoutTimes int +} + +// nolint +func NewRpcServerHandler(maxSessionNum int, sessionTimeout time.Duration, serverP *Server) *RpcServerHandler { + return &RpcServerHandler{ + maxSessionNum: maxSessionNum, + sessionTimeout: sessionTimeout, + sessionMap: make(map[getty.Session]*rpcSession), + server: serverP, + } +} + +// OnOpen call server session opened, add the session to getty server session list. also onOpen +// will check the max getty server session number +func (h *RpcServerHandler) OnOpen(session getty.Session) error { + var err error + h.rwlock.RLock() + if h.maxSessionNum <= len(h.sessionMap) { + err = errTooManySessions + } + h.rwlock.RUnlock() + if err != nil { + return perrors.WithStack(err) + } + + logger.Infof("got session:%s", session.Stat()) + h.rwlock.Lock() + h.sessionMap[session] = &rpcSession{session: session} + h.rwlock.Unlock() + return nil +} + +// OnError the getty server session has errored, so remove the session from the getty server session list +func (h *RpcServerHandler) OnError(session getty.Session, err error) { + logger.Infof("session{%s} got error{%v}, will be closed.", session.Stat(), err) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +// OnClose close the session, remove it from the getty server list +func (h *RpcServerHandler) OnClose(session getty.Session) { + logger.Infof("session{%s} is closing......", session.Stat()) + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() +} + +// OnMessage get request from getty client, update the session reqNum and reply response to client +func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) { + h.rwlock.Lock() + if _, ok := h.sessionMap[session]; ok { + h.sessionMap[session].reqNum++ + } + h.rwlock.Unlock() + + decodeResult, ok := pkg.(remoting.DecodeResult) + if !ok { + logger.Errorf("illegal package{%#v}", pkg) + return + } + if !decodeResult.IsRequest { + res := decodeResult.Result.(*remoting.Response) + if res.Event { + logger.Debugf("get rpc heartbeat response{%#v}", res) + if res.Error != nil { + logger.Errorf("rpc heartbeat response{error: %#v}", res.Error) + } + res.Handle() + return + } + logger.Errorf("illegal package but not heartbeart. {%#v}", pkg) + return + } + req := decodeResult.Result.(*remoting.Request) + + resp := remoting.NewResponse(req.ID, req.Version) + resp.Status = hessian.Response_OK + resp.Event = req.Event + resp.SerialID = req.SerialID + resp.Version = "2.0.2" + + // heartbeat + if req.Event { + logger.Debugf("get rpc heartbeat request{%#v}", resp) + reply(session, resp) + return + } + + defer func() { + if e := recover(); e != nil { + resp.Status = hessian.Response_SERVER_ERROR + if err, ok := e.(error); ok { + logger.Errorf("OnMessage panic: %+v", perrors.WithStack(err)) + resp.Error = perrors.WithStack(err) + } else if err, ok := e.(string); ok { + logger.Errorf("OnMessage panic: %+v", perrors.New(err)) + resp.Error = perrors.New(err) + } else { + logger.Errorf("OnMessage panic: %+v, this is impossible.", e) + resp.Error = fmt.Errorf("OnMessage panic unknow exception. %+v", e) + } + + if !req.TwoWay { + return + } + reply(session, resp) + } + + }() + + invoc, ok := req.Data.(*invocation.RPCInvocation) + if !ok { + panic("create invocation occur some exception for the type is not suitable one.") + } + attachments := invoc.Attachments() + attachments[constant.LOCAL_ADDR] = session.LocalAddr() + attachments[constant.REMOTE_ADDR] = session.RemoteAddr() + + result := h.server.requestHandler(invoc) + if !req.TwoWay { + return + } + resp.Result = result + reply(session, resp) +} + +// OnCron check the session health periodic. if the session's sessionTimeout has reached, just close the session +func (h *RpcServerHandler) OnCron(session getty.Session) { + var ( + flag bool + active time.Time + ) + + h.rwlock.RLock() + if _, ok := h.sessionMap[session]; ok { + active = session.GetActive() + if h.sessionTimeout.Nanoseconds() < time.Since(active).Nanoseconds() { + flag = true + logger.Warnf("session{%s} timeout{%s}, reqNum{%d}", + session.Stat(), time.Since(active).String(), h.sessionMap[session].reqNum) + } + } + h.rwlock.RUnlock() + + if flag { + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() + session.Close() + } + + heartbeatCallBack := func(err error) { + if err != nil { + logger.Warnf("failed to send heartbeat, error{%v}", err) + if h.timeoutTimes >= 3 { + h.rwlock.Lock() + delete(h.sessionMap, session) + h.rwlock.Unlock() + session.Close() + return + } + h.timeoutTimes++ + return + } + h.timeoutTimes = 0 + } + + if err := heartbeat(session, h.server.conf.heartbeatTimeout, heartbeatCallBack); err != nil { + logger.Warnf("failed to send heartbeat, error{%v}", err) + } +} + +func reply(session getty.Session, resp *remoting.Response) { + if err := session.WritePkg(resp, WritePkg_Timeout); err != nil { + logger.Errorf("WritePkg error: %#v, %#v", perrors.WithStack(err), resp) + } +} + +func heartbeat(session getty.Session, timeout time.Duration, callBack func(err error)) error { + req := remoting.NewRequest("2.0.2") + req.TwoWay = true + req.Event = true + resp := remoting.NewPendingResponse(req.ID) + remoting.AddPendingResponse(resp) + err := session.WritePkg(req, 3*time.Second) + + go func() { + var err1 error + select { + case <-getty.GetTimeWheel().After(timeout): + err1 = errHeartbeatReadTimeout + case <-resp.Done: + err1 = resp.Err + } + callBack(err1) + }() + + return perrors.WithStack(err) +} diff --git a/protocol/dubbo/listener_test.go b/remoting/getty/listener_test.go similarity index 69% rename from protocol/dubbo/listener_test.go rename to remoting/getty/listener_test.go index 5f809814607558650e09934019db96dbb2ceeeae..7e7ac5fed440a02188057d520a944b48c8bf7b64 100644 --- a/protocol/dubbo/listener_test.go +++ b/remoting/getty/listener_test.go @@ -15,27 +15,28 @@ * limitations under the License. */ -package dubbo +package getty import ( + "context" "testing" ) import ( - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/mocktracer" "github.com/stretchr/testify/assert" ) import ( "github.com/apache/dubbo-go/common/constant" "github.com/apache/dubbo-go/protocol/invocation" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/mocktracer" ) // test rebuild the ctx func TestRebuildCtx(t *testing.T) { opentracing.SetGlobalTracer(mocktracer.New()) - attach := make(map[string]string, 10) + attach := make(map[string]interface{}, 10) attach[constant.VERSION_KEY] = "1.0" attach[constant.GROUP_KEY] = "MyGroup" inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) @@ -47,8 +48,9 @@ func TestRebuildCtx(t *testing.T) { span, ctx := opentracing.StartSpanFromContext(ctx, "Test-Client") - opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, - opentracing.TextMapCarrier(inv.Attachments())) + err := injectTraceCtx(span, inv) + assert.NoError(t, err) + // rebuild the context success inv = invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) ctx = rebuildCtx(inv) @@ -56,3 +58,18 @@ func TestRebuildCtx(t *testing.T) { assert.NotNil(t, ctx) assert.NotNil(t, ctx.Value(constant.TRACING_REMOTE_SPAN_CTX)) } + +// rebuildCtx rebuild the context by attachment. +// Once we decided to transfer more context's key-value, we should change this. +// now we only support rebuild the tracing context +func rebuildCtx(inv *invocation.RPCInvocation) context.Context { + ctx := context.WithValue(context.Background(), "attachment", inv.Attachments()) + + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(filterContext(inv.Attachments()))) + if err == nil { + ctx = context.WithValue(ctx, constant.TRACING_REMOTE_SPAN_CTX, spanCtx) + } + return ctx +} diff --git a/remoting/getty/opentracing.go b/remoting/getty/opentracing.go new file mode 100644 index 0000000000000000000000000000000000000000..7db733cbe919f2bef46cfc477bda836dc2da0d45 --- /dev/null +++ b/remoting/getty/opentracing.go @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "github.com/opentracing/opentracing-go" +) +import ( + invocation_impl "github.com/apache/dubbo-go/protocol/invocation" +) + +func injectTraceCtx(currentSpan opentracing.Span, inv *invocation_impl.RPCInvocation) error { + // inject opentracing ctx + traceAttachments := filterContext(inv.Attachments()) + carrier := opentracing.TextMapCarrier(traceAttachments) + err := opentracing.GlobalTracer().Inject(currentSpan.Context(), opentracing.TextMap, carrier) + if err == nil { + fillTraceAttachments(inv.Attachments(), traceAttachments) + } + return err +} + +func extractTraceCtx(inv *invocation_impl.RPCInvocation) (opentracing.SpanContext, error) { + traceAttachments := filterContext(inv.Attachments()) + // actually, if user do not use any opentracing framework, the err will not be nil. + spanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, + opentracing.TextMapCarrier(traceAttachments)) + return spanCtx, err +} + +func filterContext(attachments map[string]interface{}) map[string]string { + var traceAttchment = make(map[string]string) + for k, v := range attachments { + if r, ok := v.(string); ok { + traceAttchment[k] = r + } + } + return traceAttchment +} + +func fillTraceAttachments(attachments map[string]interface{}, traceAttachment map[string]string) { + for k, v := range traceAttachment { + attachments[k] = v + } +} diff --git a/protocol/dubbo/pool.go b/remoting/getty/pool.go similarity index 88% rename from protocol/dubbo/pool.go rename to remoting/getty/pool.go index 6a7d211b496d3f6905c8a84e2f1bd648718ebb92..9689175bcf9838de595f292779b099ae9615d8e8 100644 --- a/protocol/dubbo/pool.go +++ b/remoting/getty/pool.go @@ -15,7 +15,7 @@ * limitations under the License. */ -package dubbo +package getty import ( "crypto/tls" @@ -38,10 +38,10 @@ import ( ) type gettyRPCClient struct { - once sync.Once - protocol string - addr string - active int64 // zero, not create or be destroyed + once sync.Once + //protocol string + addr string + active int64 // zero, not create or be destroyed pool *gettyRPCClientPool @@ -54,7 +54,7 @@ var ( errClientPoolClosed = perrors.New("client pool closed") ) -func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*gettyRPCClient, error) { +func newGettyRPCClientConn(pool *gettyRPCClientPool, addr string) (*gettyRPCClient, error) { var ( gettyClient getty.Client sslEnabled bool @@ -76,25 +76,31 @@ func newGettyRPCClientConn(pool *gettyRPCClientPool, protocol, addr string) (*ge ) } c := &gettyRPCClient{ - protocol: protocol, addr: addr, pool: pool, gettyClient: gettyClient, } go c.gettyClient.RunEventLoop(c.newSession) + idx := 1 - times := int(pool.rpcClient.opts.ConnectTimeout / 1e6) + start := time.Now() + connectTimeout := pool.rpcClient.opts.ConnectTimeout for { idx++ if c.isAvailable() { break } - if idx > times { + if time.Now().Sub(start) > connectTimeout { c.gettyClient.Close() - return nil, perrors.New(fmt.Sprintf("failed to create client connection to %s in %f seconds", addr, float32(times)/1000)) + return nil, perrors.New(fmt.Sprintf("failed to create client connection to %s in %s", addr, connectTimeout)) + } + + interval := time.Millisecond * time.Duration(idx) + if interval > time.Duration(100e6) { + interval = 100e6 // 100 ms } - time.Sleep(1e6) + time.Sleep(interval) } logger.Debug("client init ok") c.updateActive(time.Now().Unix()) @@ -117,7 +123,6 @@ func (c *gettyRPCClient) newSession(session getty.Session) error { conf ClientConfig sslEnabled bool ) - conf = c.pool.rpcClient.conf sslEnabled = c.pool.sslEnabled if conf.GettySessionParam.CompressEncoding { @@ -255,25 +260,25 @@ func (c *gettyRPCClient) updateSession(session getty.Session) { func (c *gettyRPCClient) getClientRpcSession(session getty.Session) (rpcSession, error) { var ( - err error - rpcClientSession rpcSession + err error + rpcSession rpcSession ) c.lock.RLock() defer c.lock.RUnlock() if c.sessions == nil { - return rpcClientSession, errClientClosed + return rpcSession, errClientClosed } err = errSessionNotExist for _, s := range c.sessions { if s.session == session { - rpcClientSession = *s + rpcSession = *s err = nil break } } - return rpcClientSession, perrors.WithStack(err) + return rpcSession, perrors.WithStack(err) } func (c *gettyRPCClient) isAvailable() bool { @@ -338,7 +343,8 @@ func newGettyRPCClientConnPool(rpcClient *Client, size int, ttl time.Duration) * rpcClient: rpcClient, size: size, ttl: int64(ttl.Seconds()), - conns: make([]*gettyRPCClient, 0, 16), + // init capacity : 2 + conns: make([]*gettyRPCClient, 0, 2), } } @@ -352,11 +358,15 @@ func (p *gettyRPCClientPool) close() { } } -func (p *gettyRPCClientPool) getGettyRpcClient(protocol, addr string) (*gettyRPCClient, error) { +func (p *gettyRPCClientPool) getGettyRpcClient(addr string) (*gettyRPCClient, error) { conn, err := p.get() if err == nil && conn == nil { // create new conn - conn, err = newGettyRPCClientConn(p, protocol, addr) + rpcClientConn, err := newGettyRPCClientConn(p, addr) + if err == nil { + p.put(rpcClientConn) + } + return rpcClientConn, perrors.WithStack(err) } return conn, perrors.WithStack(err) } @@ -369,14 +379,20 @@ func (p *gettyRPCClientPool) get() (*gettyRPCClient, error) { if p.conns == nil { return nil, errClientPoolClosed } - - for len(p.conns) > 0 { - conn := p.conns[len(p.conns)-1] - p.conns = p.conns[:len(p.conns)-1] + for num := len(p.conns); num > 0; { + var conn *gettyRPCClient + if num != 1 { + conn = p.conns[rand.Int31n(int32(num))] + } else { + conn = p.conns[0] + } + // This will recreate gettyRpcClient for remove last position + //p.conns = p.conns[:len(p.conns)-1] if d := now - conn.getActive(); d > p.ttl { p.remove(conn) go conn.close() + num = len(p.conns) continue } conn.updateActive(now) //update active time @@ -389,21 +405,17 @@ func (p *gettyRPCClientPool) put(conn *gettyRPCClient) { if conn == nil || conn.getActive() == 0 { return } - p.Lock() defer p.Unlock() - if p.conns == nil { return } - // check whether @conn has existed in p.conns or not. for i := range p.conns { if p.conns[i] == conn { return } } - if len(p.conns) >= p.size { // delete @conn from client pool // p.remove(conn) diff --git a/remoting/getty/pool_test.go b/remoting/getty/pool_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1115a490428da14a72f5c40e1b98b2a7fb3f80ad --- /dev/null +++ b/remoting/getty/pool_test.go @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestGetConnFromPool(t *testing.T) { + var rpcClient Client + + clientPoll := newGettyRPCClientConnPool(&rpcClient, 1, time.Duration(5*time.Second)) + + var conn1 gettyRPCClient + conn1.active = time.Now().Unix() + clientPoll.put(&conn1) + assert.Equal(t, 1, len(clientPoll.conns)) + + var conn2 gettyRPCClient + conn2.active = time.Now().Unix() + clientPoll.put(&conn2) + assert.Equal(t, 1, len(clientPoll.conns)) + conn, err := clientPoll.get() + assert.Nil(t, err) + assert.Equal(t, &conn1, conn) + time.Sleep(6 * time.Second) + conn, err = clientPoll.get() + assert.Nil(t, conn) + assert.Nil(t, err) + assert.Equal(t, 0, len(clientPoll.conns)) +} diff --git a/remoting/getty/readwriter.go b/remoting/getty/readwriter.go new file mode 100644 index 0000000000000000000000000000000000000000..072eb3e61538fc5fc358846dcb697ecff9552d4d --- /dev/null +++ b/remoting/getty/readwriter.go @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package getty + +import ( + "reflect" +) + +import ( + "github.com/apache/dubbo-getty" + hessian "github.com/apache/dubbo-go-hessian2" + perrors "github.com/pkg/errors" +) + +import ( + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/remoting" +) + +//////////////////////////////////////////// +// RpcClientPackageHandler +//////////////////////////////////////////// + +// RpcClientPackageHandler Read data from server and Write data to server +type RpcClientPackageHandler struct { + client *Client +} + +// NewRpcClientPackageHandler create a RpcClientPackageHandler +func NewRpcClientPackageHandler(client *Client) *RpcClientPackageHandler { + return &RpcClientPackageHandler{client: client} +} + +// Read data from server. if the package size from server is larger than 4096 byte, server will read 4096 byte +// and send to client each time. the Read can assemble it. +func (p *RpcClientPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + resp, length, err := (p.client.codec).Decode(data) + //err := pkg.Unmarshal(buf, p.client) + if err != nil { + err = perrors.Cause(err) + if err == hessian.ErrHeaderNotEnough || err == hessian.ErrBodyNotEnough { + return nil, 0, nil + } + + logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) + + return nil, length, err + } + + return resp, length, nil +} + +// Write send the data to server +func (p *RpcClientPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { + req, ok := pkg.(*remoting.Request) + if ok { + buf, err := (p.client.codec).EncodeRequest(req) + if err != nil { + logger.Warnf("binary.Write(req{%#v}) = err{%#v}", req, perrors.WithStack(err)) + return nil, perrors.WithStack(err) + } + return buf.Bytes(), nil + } + + res, ok := pkg.(*remoting.Response) + if ok { + buf, err := (p.client.codec).EncodeResponse(res) + if err != nil { + logger.Warnf("binary.Write(res{%#v}) = err{%#v}", req, perrors.WithStack(err)) + return nil, perrors.WithStack(err) + } + return buf.Bytes(), nil + } + + logger.Errorf("illegal pkg:%+v\n", pkg) + return nil, perrors.New("invalid rpc request") +} + +//////////////////////////////////////////// +// RpcServerPackageHandler +//////////////////////////////////////////// + +//var ( +// rpcServerPkgHandler = &RpcServerPackageHandler{} +//) + +// RpcServerPackageHandler Read data from client and Write data to client +type RpcServerPackageHandler struct { + server *Server +} + +func NewRpcServerPackageHandler(server *Server) *RpcServerPackageHandler { + return &RpcServerPackageHandler{server: server} +} + +// Read data from client. if the package size from client is larger than 4096 byte, client will read 4096 byte +// and send to client each time. the Read can assemble it. +func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) { + req, length, err := (p.server.codec).Decode(data) + //resp,len, err := (*p.).DecodeResponse(buf) + if err != nil { + if err == hessian.ErrHeaderNotEnough || err == hessian.ErrBodyNotEnough { + return nil, 0, nil + } + + logger.Errorf("pkg.Unmarshal(ss:%+v, len(@data):%d) = error:%+v", ss, len(data), err) + + return nil, 0, err + } + + return req, length, err +} + +// Write send the data to client +func (p *RpcServerPackageHandler) Write(ss getty.Session, pkg interface{}) ([]byte, error) { + res, ok := pkg.(*remoting.Response) + if ok { + buf, err := (p.server.codec).EncodeResponse(res) + if err != nil { + logger.Warnf("binary.Write(res{%#v}) = err{%#v}", res, perrors.WithStack(err)) + return nil, perrors.WithStack(err) + } + return buf.Bytes(), nil + } + + req, ok := pkg.(*remoting.Request) + if ok { + buf, err := (p.server.codec).EncodeRequest(req) + if err != nil { + logger.Warnf("binary.Write(req{%#v}) = err{%#v}", res, perrors.WithStack(err)) + return nil, perrors.WithStack(err) + } + return buf.Bytes(), nil + } + + logger.Errorf("illegal pkg:%+v\n, it is %+v", pkg, reflect.TypeOf(pkg)) + return nil, perrors.New("invalid rpc response") + +} diff --git a/remoting/kubernetes/client.go b/remoting/kubernetes/client.go index 0a0548959a3e6d839321d03a627bb6aba66d8474..fce9e80eb88b73cfbe96a4aaebeb106a902fe41d 100644 --- a/remoting/kubernetes/client.go +++ b/remoting/kubernetes/client.go @@ -47,7 +47,7 @@ type Client struct { } // newClient returns Client instance for registry -func newClient(url common.URL) (*Client, error) { +func newClient(url *common.URL) (*Client, error) { ctx, cancel := context.WithCancel(context.Background()) diff --git a/remoting/kubernetes/facade_test.go b/remoting/kubernetes/facade_test.go index 65c5d715a38bd0862245255e0276ff5e959de3a3..00e2e1171c54c2b07973b66cb96cf64e67683f00 100644 --- a/remoting/kubernetes/facade_test.go +++ b/remoting/kubernetes/facade_test.go @@ -43,8 +43,8 @@ func (r *mockFacade) SetClient(client *Client) { r.client = client } -func (r *mockFacade) GetUrl() common.URL { - return *r.URL +func (r *mockFacade) GetUrl() *common.URL { + return r.URL } func (r *mockFacade) Destroy() { @@ -68,7 +68,7 @@ func Test_Facade(t *testing.T) { mockClient := getTestClient(t) m := &mockFacade{ - URL: ®Url, + URL: regUrl, client: mockClient, } diff --git a/remoting/zookeeper/curator_discovery/service_discovery.go b/remoting/zookeeper/curator_discovery/service_discovery.go index 9566b5494389325520b4eb6a8eb170e0b305bb47..acd43c0b92bd6220efc6527efc1748ed3021f7ac 100644 --- a/remoting/zookeeper/curator_discovery/service_discovery.go +++ b/remoting/zookeeper/curator_discovery/service_discovery.go @@ -154,7 +154,6 @@ func (sd *ServiceDiscovery) updateInternalService(name, id string) { return } entry.instance = instance - return } // UnregisterService un-register service in zookeeper and delete service in cache diff --git a/remoting/zookeeper/facade.go b/remoting/zookeeper/facade.go index 2a034390c016864f95303b12b6c56533771075f3..4dc0a549f2ae53edcc7a645de4dfffc8b9da00a0 100644 --- a/remoting/zookeeper/facade.go +++ b/remoting/zookeeper/facade.go @@ -37,7 +37,7 @@ type ZkClientFacade interface { WaitGroup() *sync.WaitGroup // for wait group control, zk client listener & zk client container Done() chan struct{} // for zk client control RestartCallBack() bool - GetUrl() common.URL + GetUrl() *common.URL } // HandleClientRestart keeps the connection between client and server diff --git a/remoting/zookeeper/facade_test.go b/remoting/zookeeper/facade_test.go index 1cd8f064bb15a2ac48b0d62154309b27c55ab946..3d5798c947fb0fb33adce708e1bcdb8fb24e530f 100644 --- a/remoting/zookeeper/facade_test.go +++ b/remoting/zookeeper/facade_test.go @@ -68,8 +68,8 @@ func (r *mockFacade) Done() chan struct{} { return r.done } -func (r *mockFacade) GetUrl() common.URL { - return *r.URL +func (r *mockFacade) GetUrl() *common.URL { + return r.URL } func (r *mockFacade) Destroy() { @@ -90,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 := newMockFacade(z, &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 486a67e20a0736be20813226b622f1bfa5bd87c0..e5ddcadeaca9c3ce972cbe781413ebec7393afce 100644 --- a/remoting/zookeeper/listener.go +++ b/remoting/zookeeper/listener.go @@ -301,6 +301,9 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen go func(zkPath string, listener remoting.DataListener) { if l.listenServiceNodeEvent(zkPath) { listener.DataChange(remoting.Event{Path: zkPath, Action: remoting.EventTypeDel}) + l.pathMapLock.Lock() + defer l.pathMapLock.Unlock() + delete(l.pathMap, zkPath) } logger.Warnf("listenSelf(zk path{%s}) goroutine exit now", zkPath) }(dubboPath, listener) @@ -322,7 +325,7 @@ func (l *ZkEventListener) listenDirEvent(conf *common.URL, zkPath string, listen for { select { case <-ticker.C: - l.handleZkNodeEvent(zkEvent.Path, children, listener) + l.handleZkNodeEvent(zkPath, children, listener) case zkEvent = <-childEventCh: logger.Warnf("get a zookeeper zkEvent{type:%s, server:%s, path:%s, state:%d-%s, err:%s}", zkEvent.Type.String(), zkEvent.Server, zkEvent.Path, zkEvent.State, StateToString(zkEvent.State), zkEvent.Err) diff --git a/test/integrate/dubbo/go-client/go.mod b/test/integrate/dubbo/go-client/go.mod index 4708eb1f0f48c10acc254880ecb6dad3a03529f2..162f32ba9b7a217e7ae8bcfea5aa5b91d76b383c 100644 --- a/test/integrate/dubbo/go-client/go.mod +++ b/test/integrate/dubbo/go-client/go.mod @@ -1,3 +1,5 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-client +require github.com/apache/dubbo-go-hessian2 v1.7.0 + go 1.13 diff --git a/test/integrate/dubbo/go-client/go.sum b/test/integrate/dubbo/go-client/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..fc378395782f0f7e8032cdaed2ec4c4b0266c149 --- /dev/null +++ b/test/integrate/dubbo/go-client/go.sum @@ -0,0 +1,11 @@ +github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/apache/dubbo-go-hessian2 v1.7.0/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/test/integrate/dubbo/go-server/go.mod b/test/integrate/dubbo/go-server/go.mod index 9e1162327de374fb131c2a0b89d1be3baa578a1b..f9d950e0d1b9b7e56922120772c98699f7b3acc9 100644 --- a/test/integrate/dubbo/go-server/go.mod +++ b/test/integrate/dubbo/go-server/go.mod @@ -1,3 +1,5 @@ module github.com/apache/dubbo-go/test/integrate/dubbo/go-server +require github.com/apache/dubbo-go-hessian2 v1.7.0 + go 1.13 diff --git a/test/integrate/dubbo/go-server/go.sum b/test/integrate/dubbo/go-server/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..fc378395782f0f7e8032cdaed2ec4c4b0266c149 --- /dev/null +++ b/test/integrate/dubbo/go-server/go.sum @@ -0,0 +1,11 @@ +github.com/apache/dubbo-go-hessian2 v1.6.0-rc1.0.20200906044240-6c1fb5c3bd44/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/apache/dubbo-go-hessian2 v1.7.0/go.mod h1:7rEw9guWABQa6Aqb8HeZcsYPHsOS7XT1qtJvkmI6c5w= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=