├── .github └── workflows │ ├── ci.yaml │ ├── e2e-etcd.yaml │ ├── e2e-nacos.yaml │ └── e2e-zk.yaml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd └── dubbo2istio │ └── main.go ├── demo ├── k8s │ ├── etcd │ │ ├── dubbo-example.yaml │ │ ├── dubbo2istio.yaml │ │ └── etcd.yaml │ ├── nacos │ │ ├── dubbo-example.yaml │ │ ├── dubbo2istio.yaml │ │ └── nacos.yaml │ └── zk │ │ ├── dubbo-example.yaml │ │ ├── dubbo2istio.yaml │ │ └── zookeeper.yaml └── traffic-rules │ ├── destinationrule.yaml │ ├── metarouter-v1.yaml │ ├── metarouter-v2.yaml │ └── traffic-split.yaml ├── doc ├── dubbo2istio.png └── grafana.png ├── docker └── Dockerfile ├── dubbo2istio.yaml ├── go.mod ├── go.sum ├── pkg └── dubbo │ ├── common │ ├── constants.go │ ├── conversion.go │ ├── conversion_test.go │ ├── model.go │ ├── serviceentry.go │ ├── utils.go │ └── utils_test.go │ ├── etcd │ ├── connHandler.go │ ├── controller.go │ ├── controller_test.go │ └── watcher.go │ ├── nacos │ ├── controller.go │ ├── model │ │ └── conversion.go │ └── watcher │ │ └── naemspace.go │ └── zk │ ├── controller.go │ ├── model │ ├── conversion.go │ └── conversion_test.go │ └── watcher │ ├── provider.go │ └── service.go └── test └── e2e ├── common.go ├── common ├── aeraki-crd.yaml ├── aeraki.yaml ├── dubbo2istio-etcd.yaml ├── dubbo2istio-nacos.yaml ├── dubbo2istio-zk.yaml └── istio-config.yaml ├── etcd └── etcd_test.go ├── nacos └── nacos_test.go ├── scripts ├── aeraki.sh ├── dubbo2istio.sh ├── istio.sh ├── minikube.sh └── pre.sh ├── util ├── common_utils.go ├── kube_utils.go └── retry.go └── zk └── zk_test.go /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | on: [push, pull_request] 4 | jobs: 5 | build-and-test: 6 | name: build and test 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check out code 10 | uses: actions/checkout@v2 11 | - name: Setup Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: 1.16.3 15 | - name: Build 16 | run: go build -race ./... 17 | - name: Test 18 | run: go test -race `go list ./... | grep -v e2e` 19 | 20 | go-lint: 21 | name: go-lint 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Check out code into the Go module directory 25 | uses: actions/checkout@v2 26 | - name: golint 27 | uses: Jerome1337/golint-action@v1.0.2 28 | with: 29 | golint-path: './...' 30 | - name: golangci-lint 31 | uses: golangci/golangci-lint-action@v3.1.0 32 | with: 33 | args: --timeout=10m --tests="false" 34 | style-check: 35 | name: gofmt and goimports 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Check out code 39 | uses: actions/checkout@v2 40 | - name: Setup Go 41 | uses: actions/setup-go@v2 42 | with: 43 | go-version: 1.16.3 44 | - name: Install dependencies 45 | run: | 46 | go version 47 | go get golang.org/x/tools/cmd/goimports 48 | - name: gofmt and goimports 49 | run: | 50 | gofmt -l -d ./ 51 | goimports -l -d ./ 52 | checkgomod: 53 | name: check go.mod and go.sum 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Check out code 57 | uses: actions/checkout@v2 58 | - name: Setup Go 59 | uses: actions/setup-go@v2 60 | with: 61 | go-version: 1.16.3 62 | - run: go mod tidy 63 | - name: Check for changes in go.mod or go.sum 64 | run: | 65 | git diff --name-only --exit-code go.mod || ( echo "Run go tidy" && false ) 66 | git diff --name-only --exit-code go.sum || ( echo "Run go tidy" && false ) 67 | -------------------------------------------------------------------------------- /.github/workflows/e2e-etcd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: e2e-etcd 3 | 4 | on: 5 | pull_request: 6 | paths-ignore: 7 | - '**.md' 8 | - 'docs/**' 9 | push: 10 | paths-ignore: 11 | - '**.md' 12 | - 'docs/**' 13 | branches: 14 | - '*' 15 | 16 | env: 17 | ISTIO_VERSION: 1.10.0 18 | SCRIPTS_DIR: test/e2e/scripts 19 | COMMON_DIR: test/e2e/common 20 | 21 | jobs: 22 | TestNacos: 23 | runs-on: ubuntu-latest 24 | timeout-minutes: 60 25 | strategy: 26 | fail-fast: true 27 | name: e2e 28 | steps: 29 | - name: Check out code 30 | uses: actions/checkout@v2 31 | - name: Setup Go 32 | uses: actions/setup-go@v2 33 | with: 34 | go-version: 1.16.3 35 | - name: Install dependencies 36 | run: | 37 | go version 38 | - name: build docker 39 | run: make docker-build-e2e 40 | - name: Prepare envrionment 41 | run: bash ${SCRIPTS_DIR}/pre.sh 42 | - name: Install Minikube 43 | run: bash ${SCRIPTS_DIR}/minikube.sh start 44 | - name: Install aeraki 45 | run: bash ${SCRIPTS_DIR}/aeraki.sh 46 | - name: Install Istio 47 | run: bash ${SCRIPTS_DIR}/istio.sh -y -f ${COMMON_DIR}/istio-config.yaml 48 | - name: Install dubbo2istio 49 | run: bash ${SCRIPTS_DIR}/dubbo2istio.sh etcd 50 | - name: test 51 | run: go test -v github.com/aeraki-mesh/dubbo2istio/test/e2e/etcd/ 52 | -------------------------------------------------------------------------------- /.github/workflows/e2e-nacos.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: e2e-nacos 4 | 5 | on: 6 | pull_request: 7 | paths-ignore: 8 | - '**.md' 9 | - 'docs/**' 10 | push: 11 | paths-ignore: 12 | - '**.md' 13 | - 'docs/**' 14 | branches: 15 | - '*' 16 | 17 | env: 18 | ISTIO_VERSION: 1.10.0 19 | SCRIPTS_DIR: test/e2e/scripts 20 | COMMON_DIR: test/e2e/common 21 | 22 | jobs: 23 | TestNacos: 24 | runs-on: ubuntu-latest 25 | timeout-minutes: 60 26 | strategy: 27 | fail-fast: true 28 | name: e2e 29 | steps: 30 | - name: Check out code 31 | uses: actions/checkout@v2 32 | - name: Setup Go 33 | uses: actions/setup-go@v2 34 | with: 35 | go-version: 1.16.3 36 | - name: Install dependencies 37 | run: | 38 | go version 39 | - name: build docker 40 | run: make docker-build-e2e 41 | - name: Prepare envrionment 42 | run: bash ${SCRIPTS_DIR}/pre.sh 43 | - name: Install Minikube 44 | run: bash ${SCRIPTS_DIR}/minikube.sh start 45 | - name: Install aeraki 46 | run: bash ${SCRIPTS_DIR}/aeraki.sh 47 | - name: Install Istio 48 | run: bash ${SCRIPTS_DIR}/istio.sh -y -f ${COMMON_DIR}/istio-config.yaml 49 | - name: Install dubbo2istio 50 | run: bash ${SCRIPTS_DIR}/dubbo2istio.sh nacos 51 | - name: test 52 | run: go test -v github.com/aeraki-mesh/dubbo2istio/test/e2e/nacos/ 53 | -------------------------------------------------------------------------------- /.github/workflows/e2e-zk.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: e2e-zookeeper 4 | 5 | on: 6 | pull_request: 7 | paths-ignore: 8 | - '**.md' 9 | - 'docs/**' 10 | push: 11 | paths-ignore: 12 | - '**.md' 13 | - 'docs/**' 14 | branches: 15 | - '*' 16 | 17 | env: 18 | ISTIO_VERSION: 1.10.0 19 | SCRIPTS_DIR: test/e2e/scripts 20 | COMMON_DIR: test/e2e/common 21 | 22 | jobs: 23 | TestZooKeeper: 24 | runs-on: ubuntu-latest 25 | timeout-minutes: 60 26 | strategy: 27 | fail-fast: true 28 | name: e2e 29 | steps: 30 | - name: Check out code 31 | uses: actions/checkout@v2 32 | - name: Setup Go 33 | uses: actions/setup-go@v2 34 | with: 35 | go-version: 1.16.3 36 | - name: Install dependencies 37 | run: | 38 | go version 39 | - name: build docker 40 | run: make docker-build-e2e 41 | - name: Prepare envrionment 42 | run: bash ${SCRIPTS_DIR}/pre.sh 43 | - name: Install Minikube 44 | run: bash ${SCRIPTS_DIR}/minikube.sh start 45 | - name: Install aeraki 46 | run: bash ${SCRIPTS_DIR}/aeraki.sh 47 | - name: Install Istio 48 | run: bash ${SCRIPTS_DIR}/istio.sh -y -f ${COMMON_DIR}/istio-config.yaml 49 | - name: Install dubbo2istio 50 | run: bash ${SCRIPTS_DIR}/dubbo2istio.sh zk 51 | - name: test 52 | run: go test -v github.com/aeraki-mesh/dubbo2istio/test/e2e/zk/ 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | out/ 3 | .swp 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2016-2020 Istio Authors 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD?=go 3 | GOBUILD?=$(GOCMD) build 4 | GOCLEAN?=$(GOCMD) clean 5 | GOTEST?=$(GOCMD) test 6 | GOGET?=$(GOCMD) get 7 | GOBIN?=$(GOPATH)/bin 8 | 9 | # Build parameters 10 | OUT?=./out 11 | DOCKER_TMP?=$(OUT)/docker_temp/ 12 | DOCKER_TAG?=aeraki/dubbo2istio 13 | DOCKER_TAG_E2E?=aeraki/dubbo2istio:`git log --format="%H" -n 1` 14 | BINARY_NAME?=$(OUT)/dubbo2istio 15 | BINARY_NAME_DARWIN?=$(BINARY_NAME)-darwin 16 | MAIN_PATH_CONSUL_MCP=./cmd/dubbo2istio/main.go 17 | 18 | build: 19 | CGO_ENABLED=0 GOOS=linux $(GOBUILD) -o $(BINARY_NAME) $(MAIN_PATH_CONSUL_MCP) 20 | build-mac: 21 | CGO_ENABLED=0 GOOS=darwin $(GOBUILD) -o $(BINARY_NAME_DARWIN) $(MAIN_PATH_CONSUL_MCP) 22 | docker-build: build 23 | rm -rf $(DOCKER_TMP) 24 | mkdir $(DOCKER_TMP) 25 | cp ./docker/Dockerfile $(DOCKER_TMP) 26 | cp $(BINARY_NAME) $(DOCKER_TMP) 27 | docker build -t $(DOCKER_TAG) $(DOCKER_TMP) 28 | rm -rf $(DOCKER_TMP) 29 | docker-build-e2e: build 30 | rm -rf $(DOCKER_TMP) 31 | mkdir $(DOCKER_TMP) 32 | cp ./docker/Dockerfile $(DOCKER_TMP) 33 | cp $(BINARY_NAME) $(DOCKER_TMP) 34 | docker build -t $(DOCKER_TAG_E2E) $(DOCKER_TMP) 35 | docker-push: docker-build 36 | docker push $(DOCKER_TAG) 37 | style-check: 38 | gofmt -l -d ./ 39 | goimports -l -d ./ 40 | lint: 41 | golint ./... 42 | golangci-lint run -v --tests="false" 43 | test: 44 | go test --race ./... 45 | clean: 46 | rm -rf $(OUT) 47 | 48 | .PHONY: build docker-build docker-push clean 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI Tests](https://github.com/aeraki-mesh/dubbo2istio/workflows/e2e-zookeeper/badge.svg?branch=master)](https://github.com/aeraki-mesh/dubbo2istio/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22e2e-zookeeper%22) 2 | [![CI Tests](https://github.com/aeraki-mesh/dubbo2istio/workflows/e2e-nacos/badge.svg?branch=master)](https://github.com/aeraki-mesh/dubbo2istio/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22e2e-nacos%22) 3 | [![CI Tests](https://github.com/aeraki-mesh/dubbo2istio/workflows/e2e-etcd/badge.svg?branch=master)](https://github.com/aeraki-mesh/dubbo2istio/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22e2e-etcd%22) 4 | # Dubbo2Istio 5 | 6 | Dubbo2istio 将 Dubbo 服务注册表中的 Dubbo 服务自动同步到 Istio 服务网格中,目前已经支持 ZooKeeper,Nacos 和 Etcd。 7 | 8 | Aeraki 根据 Dubbo 服务信息和用户设置的路由规则生成数据面相关的配置,通过 Istio 下发给数据面 Envoy 中的 Dubbo proxy。 9 | 10 | 如下图所示, [Aeraki + Dubbo2istio](https://github.com/aeraki-mesh/aeraki) 可以协助您将 Dubbo 应用迁移到 Istio 服务网格中, 11 | 享用到服务网格提供的高级流量管理、可见性、安全等能力,而这些无需改动一行 Dubbo 源代码。 12 | 13 | ![ dubbo2istio ](doc/dubbo2istio.png) 14 | 15 | # Demo 应用 16 | 17 | Aeraki 提供了一个 Dubbo Demo 应用,用于使用该 Demo 来测试 Dubbo 应用的流量控制、metrics 指标采集和权限控制等微服务治理功能。 18 | * [Demo k8s 部署文件下载](https://github.com/aeraki-mesh/dubbo2istio/tree/master/demo) 19 | * [Demo Dubbo 程序源码下载](https://github.com/aeraki-mesh/dubbo-envoyfilter-example) 20 | 21 | 备注:该 Demo 应用基于开源 Istio + Aeraki 运行,也可以在开通了 Dubbo 服务支持的 22 | [腾讯云 TCM (Tencent Cloud Mesh)](https://console.cloud.tencent.com/tke2/mesh?rid=8) 托管服务网格上运行。 23 | 24 | Demo 安装以及 Dubbo 流量管理参见 aeraki 官网教程:https://www.aeraki.net/zh/docs/v1.0/tutorials 25 | 26 | ## 一些限制 27 | 28 | * 多集群环境下,同一个 dubbo service 的多个 provider 实例需要部署在相同的 namesapce 中。 29 | 该限制的原因是 aeraki 采用了 dubbo interface 作为全局唯一的服务名,客户端使用该服务名作为 dns 名对服务端进行访问。 30 | 而在 Istio 中,一个服务必须隶属于一个 namespace,因此我们在进行多集群部署时,同一个 dubbo service 的多个实例不能存在于不同的 namespace 中。 31 | 如果违反了该部署限制,会导致客户端跨 namespace 访问服务器端实例时由于 mtls 证书验证失败而出错。 32 | -------------------------------------------------------------------------------- /cmd/dubbo2istio/main.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "os" 20 | "os/signal" 21 | "syscall" 22 | 23 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/etcd" 24 | 25 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/nacos" 26 | 27 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/zk" 28 | istioclient "istio.io/client-go/pkg/clientset/versioned" 29 | "istio.io/pkg/log" 30 | "sigs.k8s.io/controller-runtime/pkg/client/config" 31 | ) 32 | 33 | const ( 34 | defaultRegistryType = "zookeeper" 35 | defaultRegistryName = "default" 36 | ) 37 | 38 | func main() { 39 | var registryType, registryName, registryAddr string 40 | flag.StringVar(®istryType, "type", defaultRegistryType, "Type of the registry: zookeeper or nacos") 41 | flag.StringVar(®istryName, "name", defaultRegistryName, "Name is the globally unique name for a dubbo registry") 42 | flag.StringVar(®istryAddr, "addr", "", "Registry address in the form of ip:port or dns:port") 43 | flag.Parse() 44 | 45 | stopChan := make(chan struct{}, 1) 46 | ic, err := getIstioClient() 47 | if err != nil { 48 | log.Errorf("failed to create istio client: %v", err) 49 | return 50 | } 51 | 52 | if registryType == "zookeeper" { 53 | log.Infof("dubbo2istio runs in Zookeeper mode: registry: %s, addr: %s", registryName, registryAddr) 54 | zk.NewController(registryName, registryAddr, ic).Run(stopChan) 55 | } else if registryType == "nacos" { 56 | log.Infof("dubbo2istio runs in Nacos mode: registry: %s, addr: %s", registryName, registryAddr) 57 | nacosController, err := nacos.NewController(registryName, registryAddr, ic) 58 | if err != nil { 59 | log.Errorf("failed to create nacos controller :%v", err) 60 | } else { 61 | nacosController.Run(stopChan) 62 | } 63 | } else if registryType == "etcd" { 64 | log.Infof("dubbo2istio runs in Etcd mode: registry: %s, addr: %s", registryName, registryAddr) 65 | etcd.NewController(registryName, registryAddr, ic).Run(stopChan) 66 | } else { 67 | log.Errorf("unrecognized registry type: %s , dubbo2istio only supports zookeeper or nacos", registryType) 68 | } 69 | 70 | signalChan := make(chan os.Signal, 1) 71 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 72 | <-signalChan 73 | stopChan <- struct{}{} 74 | } 75 | 76 | func getIstioClient() (*istioclient.Clientset, error) { 77 | config, err := config.GetConfig() 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | ic, err := istioclient.NewForConfig(config) 83 | if err != nil { 84 | return nil, err 85 | } 86 | return ic, nil 87 | } 88 | -------------------------------------------------------------------------------- /demo/k8s/etcd/dubbo-example.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dubbo-sample-provider-v1 6 | labels: 7 | app: dubbo-sample-provider 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: dubbo-sample-provider 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 17 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 18 | labels: 19 | app: dubbo-sample-provider 20 | version: v1 21 | spec: 22 | containers: 23 | - name: dubbo-sample-provider 24 | image: aeraki/dubbo-sample-provider 25 | imagePullPolicy: Always 26 | env: 27 | - name: REGISTRY_ADDR 28 | value: etcd3://etcd:2379 29 | - name: REGISTER 30 | value: "true" 31 | - name: AERAKI_META_APP_NAMESPACE 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.namespace 35 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: spec.serviceAccountName 39 | - name: AERAKI_META_WORKLOAD_SELECTOR 40 | value: "dubbo-sample-provider" # The deployment must have a label: app:dubbo-sample-provider 41 | - name: AERAKI_META_APP_VERSION 42 | value: v1 43 | - name: AERAKI_META_LOCALITY 44 | value: bj/800002 45 | - name: SERVICE_GROUP 46 | value: user 47 | ports: 48 | - containerPort: 20880 49 | --- 50 | apiVersion: apps/v1 51 | kind: Deployment 52 | metadata: 53 | name: dubbo-sample-provider-v2 54 | labels: 55 | app: dubbo-sample-provider 56 | spec: 57 | selector: 58 | matchLabels: 59 | app: dubbo-sample-provider 60 | replicas: 1 61 | template: 62 | metadata: 63 | annotations: 64 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 65 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 66 | labels: 67 | app: dubbo-sample-provider 68 | version: v2 69 | spec: 70 | containers: 71 | - name: dubbo-sample-provider 72 | image: aeraki/dubbo-sample-provider 73 | imagePullPolicy: Always 74 | env: 75 | - name: REGISTRY_ADDR 76 | value: etcd3://etcd:2379 77 | - name: REGISTER 78 | value: "true" 79 | - name: AERAKI_META_APP_NAMESPACE 80 | valueFrom: 81 | fieldRef: 82 | fieldPath: metadata.namespace 83 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 84 | valueFrom: 85 | fieldRef: 86 | fieldPath: spec.serviceAccountName 87 | - name: AERAKI_META_WORKLOAD_SELECTOR 88 | value: "dubbo-sample-provider" # The deployment must have a label: app:dubbo-sample-provider 89 | - name: AERAKI_META_APP_VERSION 90 | value: v2 91 | - name: AERAKI_META_LOCALITY 92 | value: bj/800005 93 | - name: SERVICE_GROUP 94 | value: batchjob 95 | ports: 96 | - containerPort: 20880 97 | --- 98 | apiVersion: apps/v1 99 | kind: Deployment 100 | metadata: 101 | name: dubbo-sample-second-provider 102 | labels: 103 | app: dubbo-sample-second-provider 104 | spec: 105 | selector: 106 | matchLabels: 107 | app: dubbo-sample-second-provider 108 | replicas: 1 109 | template: 110 | metadata: 111 | annotations: 112 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 113 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy-debug:1.2.0 114 | labels: 115 | app: dubbo-sample-second-provider 116 | version: v2 117 | service_group: batchjob 118 | spec: 119 | containers: 120 | - name: dubbo-sample-second-provider 121 | image: aeraki/dubbo-sample-second-provider 122 | env: 123 | - name: REGISTRY_ADDR 124 | value: etcd3://etcd:2379 125 | - name: REGISTER 126 | value: "true" 127 | - name: AERAKI_META_APP_NAMESPACE 128 | valueFrom: 129 | fieldRef: 130 | fieldPath: metadata.namespace 131 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 132 | valueFrom: 133 | fieldRef: 134 | fieldPath: spec.serviceAccountName 135 | - name: AERAKI_META_WORKLOAD_SELECTOR 136 | value: "dubbo-sample-second-provider" # The deployment must have a label: app:dubbo-sample-second-provider 137 | - name: AERAKI_META_APP_VERSION 138 | value: v1 139 | - name: AERAKI_META_LOCALITY 140 | value: bj/800005 141 | - name: SERVICE_GROUP 142 | value: batchjob 143 | ports: 144 | - containerPort: 20880 145 | --- 146 | apiVersion: v1 147 | kind: ServiceAccount 148 | metadata: 149 | name: dubbo-consumer 150 | --- 151 | apiVersion: apps/v1 152 | kind: Deployment 153 | metadata: 154 | name: dubbo-sample-consumer 155 | labels: 156 | app: dubbo-sample-consumer 157 | spec: 158 | selector: 159 | matchLabels: 160 | app: dubbo-sample-consumer 161 | replicas: 1 162 | template: 163 | metadata: 164 | annotations: 165 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 166 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 167 | labels: 168 | app: dubbo-sample-consumer 169 | istio-locality: bj.800002 170 | spec: 171 | serviceAccountName: dubbo-consumer 172 | containers: 173 | - name: dubbo-sample-consumer 174 | image: aeraki/dubbo-sample-consumer 175 | env: 176 | - name: mode 177 | value: demo 178 | ports: 179 | - containerPort: 9009 180 | -------------------------------------------------------------------------------- /demo/k8s/etcd/dubbo2istio.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dubbo2istio 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | labels: 11 | app: dubbo2istio 12 | name: dubbo2istio 13 | rules: 14 | - apiGroups: 15 | - networking.istio.io 16 | resources: 17 | - serviceentries 18 | verbs: 19 | - get 20 | - watch 21 | - list 22 | - update 23 | - patch 24 | - create 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | labels: 31 | app: dubbo2istio 32 | name: dubbo2istio 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: dubbo2istio 37 | subjects: 38 | - kind: ServiceAccount 39 | name: dubbo2istio 40 | namespace: meta-dubbo 41 | --- 42 | apiVersion: apps/v1 43 | kind: Deployment 44 | metadata: 45 | name: dubbo2istio 46 | labels: 47 | app: dubbo2istio 48 | spec: 49 | selector: 50 | matchLabels: 51 | app: dubbo2istio 52 | replicas: 1 53 | template: 54 | metadata: 55 | annotations: 56 | sidecar.istio.io/inject: "false" 57 | labels: 58 | app: dubbo2istio 59 | spec: 60 | serviceAccountName: dubbo2istio 61 | containers: 62 | - name: dubbo2istio 63 | image: "aeraki/dubbo2istio" 64 | imagePullPolicy: Always 65 | env: 66 | - name: REGISTRY_ADDR 67 | value: "etcd:2379" 68 | - name: REGISTRY_TYPE 69 | value: "etcd" 70 | -------------------------------------------------------------------------------- /demo/k8s/etcd/etcd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | annotations: 5 | sidecar.istio.io/inject: "false" 6 | labels: 7 | app: etcd 8 | etcd_node: etcd 9 | name: etcd 10 | spec: 11 | containers: 12 | - command: 13 | - /usr/local/bin/etcd 14 | - --name 15 | - etcd 16 | - --initial-advertise-peer-urls 17 | - http://etcd:2380 18 | - --listen-peer-urls 19 | - http://0.0.0.0:2380 20 | - --listen-client-urls 21 | - http://0.0.0.0:2379 22 | - --advertise-client-urls 23 | - http://etcd:2379 24 | - --initial-cluster 25 | - etcd=http://etcd:2380 26 | - --initial-cluster-state 27 | - new 28 | - --debug=true 29 | - --log-output=stdout 30 | image: quay.io/coreos/etcd:latest 31 | name: etcd 32 | ports: 33 | - containerPort: 2379 34 | name: client 35 | protocol: TCP 36 | - containerPort: 2380 37 | name: server 38 | protocol: TCP 39 | restartPolicy: Always 40 | 41 | --- 42 | 43 | apiVersion: v1 44 | kind: Service 45 | metadata: 46 | labels: 47 | etcd_node: etcd 48 | name: etcd 49 | spec: 50 | ports: 51 | - name: tcp-client 52 | port: 2379 53 | protocol: TCP 54 | targetPort: 2379 55 | - name: tcp-server 56 | port: 2380 57 | protocol: TCP 58 | targetPort: 2380 59 | selector: 60 | etcd_node: etcd 61 | 62 | --- 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | labels: 67 | etcd_node: etcd 68 | name: etcd-aeraki 69 | spec: 70 | ports: 71 | - name: tcp-client 72 | port: 2379 73 | protocol: TCP 74 | targetPort: 2379 75 | - name: tcp-server 76 | port: 2380 77 | protocol: TCP 78 | targetPort: 2380 79 | selector: 80 | etcd_node: etcd -------------------------------------------------------------------------------- /demo/k8s/nacos/dubbo-example.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dubbo-sample-provider-v1 6 | labels: 7 | app: dubbo-sample-provider 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: dubbo-sample-provider 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 17 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 18 | labels: 19 | app: dubbo-sample-provider 20 | version: v1 21 | spec: 22 | containers: 23 | - name: dubbo-sample-provider 24 | image: aeraki/dubbo-sample-provider 25 | imagePullPolicy: Always 26 | env: 27 | - name: REGISTRY_ADDR 28 | value: nacos://nacos:8848 29 | - name: REGISTER 30 | value: "true" 31 | - name: AERAKI_META_APP_NAMESPACE 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.namespace 35 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: spec.serviceAccountName 39 | - name: AERAKI_META_WORKLOAD_SELECTOR 40 | value: "dubbo-sample-provider" # The deployment must have a label: app:dubbo-sample-provider 41 | - name: AERAKI_META_APP_VERSION 42 | value: v1 43 | - name: AERAKI_META_LOCALITY 44 | value: bj/800002 45 | - name: SERVICE_GROUP 46 | value: user 47 | ports: 48 | - containerPort: 20880 49 | --- 50 | apiVersion: apps/v1 51 | kind: Deployment 52 | metadata: 53 | name: dubbo-sample-provider-v2 54 | labels: 55 | app: dubbo-sample-provider 56 | spec: 57 | selector: 58 | matchLabels: 59 | app: dubbo-sample-provider 60 | replicas: 1 61 | template: 62 | metadata: 63 | annotations: 64 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 65 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 66 | labels: 67 | app: dubbo-sample-provider 68 | version: v2 69 | spec: 70 | containers: 71 | - name: dubbo-sample-provider 72 | image: aeraki/dubbo-sample-provider 73 | imagePullPolicy: Always 74 | env: 75 | - name: REGISTRY_ADDR 76 | value: nacos://nacos:8848 77 | - name: REGISTER 78 | value: "true" 79 | - name: AERAKI_META_APP_NAMESPACE 80 | valueFrom: 81 | fieldRef: 82 | fieldPath: metadata.namespace 83 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 84 | valueFrom: 85 | fieldRef: 86 | fieldPath: spec.serviceAccountName 87 | - name: AERAKI_META_WORKLOAD_SELECTOR 88 | value: "dubbo-sample-provider" # The deployment must have a label: app:dubbo-sample-provider 89 | - name: AERAKI_META_APP_VERSION 90 | value: v2 91 | - name: AERAKI_META_LOCALITY 92 | value: bj/800005 93 | - name: SERVICE_GROUP 94 | value: batchjob 95 | ports: 96 | - containerPort: 20880 97 | --- 98 | apiVersion: apps/v1 99 | kind: Deployment 100 | metadata: 101 | name: dubbo-sample-second-provider 102 | labels: 103 | app: dubbo-sample-second-provider 104 | spec: 105 | selector: 106 | matchLabels: 107 | app: dubbo-sample-second-provider 108 | replicas: 1 109 | template: 110 | metadata: 111 | annotations: 112 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 113 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy-debug:1.2.0 114 | labels: 115 | app: dubbo-sample-second-provider 116 | version: v2 117 | service_group: batchjob 118 | spec: 119 | containers: 120 | - name: dubbo-sample-second-provider 121 | image: aeraki/dubbo-sample-second-provider 122 | env: 123 | - name: REGISTRY_ADDR 124 | value: nacos://nacos:8848 125 | - name: REGISTER 126 | value: "true" 127 | - name: AERAKI_META_APP_NAMESPACE 128 | valueFrom: 129 | fieldRef: 130 | fieldPath: metadata.namespace 131 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 132 | valueFrom: 133 | fieldRef: 134 | fieldPath: spec.serviceAccountName 135 | - name: AERAKI_META_WORKLOAD_SELECTOR 136 | value: "dubbo-sample-second-provider" # The deployment must have a label: app:dubbo-sample-second-provider 137 | - name: AERAKI_META_APP_VERSION 138 | value: v1 139 | - name: AERAKI_META_LOCALITY 140 | value: bj/800005 141 | - name: SERVICE_GROUP 142 | value: batchjob 143 | ports: 144 | - containerPort: 20880 145 | --- 146 | apiVersion: v1 147 | kind: ServiceAccount 148 | metadata: 149 | name: dubbo-consumer 150 | --- 151 | apiVersion: apps/v1 152 | kind: Deployment 153 | metadata: 154 | name: dubbo-sample-consumer 155 | labels: 156 | app: dubbo-sample-consumer 157 | spec: 158 | selector: 159 | matchLabels: 160 | app: dubbo-sample-consumer 161 | replicas: 1 162 | template: 163 | metadata: 164 | annotations: 165 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 166 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 167 | labels: 168 | app: dubbo-sample-consumer 169 | istio-locality: bj.800002 170 | spec: 171 | serviceAccountName: dubbo-consumer 172 | containers: 173 | - name: dubbo-sample-consumer 174 | image: aeraki/dubbo-sample-consumer 175 | env: 176 | - name: mode 177 | value: demo 178 | ports: 179 | - containerPort: 9009 180 | -------------------------------------------------------------------------------- /demo/k8s/nacos/dubbo2istio.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dubbo2istio 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | labels: 11 | app: dubbo2istio 12 | name: dubbo2istio 13 | rules: 14 | - apiGroups: 15 | - networking.istio.io 16 | resources: 17 | - serviceentries 18 | verbs: 19 | - get 20 | - watch 21 | - list 22 | - update 23 | - patch 24 | - create 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | labels: 31 | app: dubbo2istio 32 | name: dubbo2istio 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: dubbo2istio 37 | subjects: 38 | - kind: ServiceAccount 39 | name: dubbo2istio 40 | namespace: meta-dubbo 41 | --- 42 | apiVersion: apps/v1 43 | kind: Deployment 44 | metadata: 45 | name: dubbo2istio 46 | labels: 47 | app: dubbo2istio 48 | spec: 49 | selector: 50 | matchLabels: 51 | app: dubbo2istio 52 | replicas: 1 53 | template: 54 | metadata: 55 | annotations: 56 | sidecar.istio.io/inject: "false" 57 | labels: 58 | app: dubbo2istio 59 | spec: 60 | serviceAccountName: dubbo2istio 61 | containers: 62 | - name: dubbo2istio 63 | image: "aeraki/dubbo2istio" 64 | imagePullPolicy: Always 65 | env: 66 | - name: REGISTRY_TYPE 67 | value: "nacos" 68 | - name: REGISTRY_ADDR 69 | value: "nacos:8848" 70 | -------------------------------------------------------------------------------- /demo/k8s/nacos/nacos.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: nacos 6 | labels: 7 | app: nacos 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: nacos 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | sidecar.istio.io/inject: "false" 17 | labels: 18 | app: nacos 19 | spec: 20 | containers: 21 | - name: nacos 22 | image: nacos/nacos-server:2.0.1 23 | env: 24 | - name: MODE 25 | value: standalone 26 | ports: 27 | - containerPort: 8848 28 | --- 29 | apiVersion: v1 30 | kind: Service 31 | metadata: 32 | name: nacos 33 | spec: 34 | selector: 35 | app: nacos 36 | ports: 37 | - name: tcp 38 | port: 8848 39 | protocol: TCP 40 | targetPort: 8848 -------------------------------------------------------------------------------- /demo/k8s/zk/dubbo-example.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: dubbo-sample-provider-v1 6 | labels: 7 | app: dubbo-sample-provider 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: dubbo-sample-provider 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 17 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 18 | labels: 19 | app: dubbo-sample-provider 20 | version: v1 21 | spec: 22 | containers: 23 | - name: dubbo-sample-provider 24 | image: aeraki/dubbo-sample-provider 25 | imagePullPolicy: Always 26 | env: 27 | - name: REGISTRY_ADDR 28 | value: zookeeper://zookeeper:2181 29 | - name: REGISTER 30 | value: "true" 31 | - name: AERAKI_META_APP_NAMESPACE 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.namespace 35 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: spec.serviceAccountName 39 | - name: AERAKI_META_WORKLOAD_SELECTOR 40 | value: "dubbo-sample-provider" # The deployment must have a label: app:dubbo-sample-provider 41 | - name: AERAKI_META_APP_VERSION 42 | value: v1 43 | - name: AERAKI_META_LOCALITY 44 | value: bj/800002 45 | - name: SERVICE_GROUP 46 | value: user 47 | ports: 48 | - containerPort: 20880 49 | --- 50 | apiVersion: apps/v1 51 | kind: Deployment 52 | metadata: 53 | name: dubbo-sample-provider-v2 54 | labels: 55 | app: dubbo-sample-provider 56 | spec: 57 | selector: 58 | matchLabels: 59 | app: dubbo-sample-provider 60 | replicas: 1 61 | template: 62 | metadata: 63 | annotations: 64 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 65 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 66 | labels: 67 | app: dubbo-sample-provider 68 | version: v2 69 | spec: 70 | containers: 71 | - name: dubbo-sample-provider 72 | image: aeraki/dubbo-sample-provider 73 | imagePullPolicy: Always 74 | env: 75 | - name: REGISTRY_ADDR 76 | value: zookeeper://zookeeper:2181 77 | - name: REGISTER 78 | value: "true" 79 | - name: AERAKI_META_APP_NAMESPACE 80 | valueFrom: 81 | fieldRef: 82 | fieldPath: metadata.namespace 83 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 84 | valueFrom: 85 | fieldRef: 86 | fieldPath: spec.serviceAccountName 87 | - name: AERAKI_META_WORKLOAD_SELECTOR 88 | value: "dubbo-sample-provider" # The deployment must have a label: app:dubbo-sample-provider 89 | - name: AERAKI_META_APP_VERSION 90 | value: v2 91 | - name: AERAKI_META_LOCALITY 92 | value: bj/800005 93 | - name: SERVICE_GROUP 94 | value: batchjob 95 | ports: 96 | - containerPort: 20880 97 | --- 98 | apiVersion: apps/v1 99 | kind: Deployment 100 | metadata: 101 | name: dubbo-sample-second-provider 102 | labels: 103 | app: dubbo-sample-second-provider 104 | spec: 105 | selector: 106 | matchLabels: 107 | app: dubbo-sample-second-provider 108 | replicas: 1 109 | template: 110 | metadata: 111 | annotations: 112 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 113 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy-debug:1.2.0 114 | labels: 115 | app: dubbo-sample-second-provider 116 | version: v2 117 | service_group: batchjob 118 | spec: 119 | containers: 120 | - name: dubbo-sample-second-provider 121 | image: aeraki/dubbo-sample-second-provider 122 | env: 123 | - name: REGISTRY_ADDR 124 | value: zookeeper://zookeeper:2181 125 | - name: REGISTER 126 | value: "true" 127 | - name: AERAKI_META_APP_NAMESPACE 128 | valueFrom: 129 | fieldRef: 130 | fieldPath: metadata.namespace 131 | - name: AERAKI_META_APP_SERVICE_ACCOUNT 132 | valueFrom: 133 | fieldRef: 134 | fieldPath: spec.serviceAccountName 135 | - name: AERAKI_META_WORKLOAD_SELECTOR 136 | value: "dubbo-sample-second-provider" # The deployment must have a label: app:dubbo-sample-second-provider 137 | - name: AERAKI_META_APP_VERSION 138 | value: v1 139 | - name: AERAKI_META_LOCALITY 140 | value: bj/800005 141 | - name: SERVICE_GROUP 142 | value: batchjob 143 | ports: 144 | - containerPort: 20880 145 | --- 146 | apiVersion: v1 147 | kind: ServiceAccount 148 | metadata: 149 | name: dubbo-consumer 150 | --- 151 | apiVersion: apps/v1 152 | kind: Deployment 153 | metadata: 154 | name: dubbo-sample-consumer 155 | labels: 156 | app: dubbo-sample-consumer 157 | spec: 158 | selector: 159 | matchLabels: 160 | app: dubbo-sample-consumer 161 | replicas: 1 162 | template: 163 | metadata: 164 | annotations: 165 | sidecar.istio.io/bootstrapOverride: aeraki-bootstrap-config 166 | sidecar.istio.io/proxyImage: aeraki/meta-protocol-proxy:1.2.0 167 | labels: 168 | app: dubbo-sample-consumer 169 | istio-locality: bj.800002 170 | spec: 171 | serviceAccountName: dubbo-consumer 172 | containers: 173 | - name: dubbo-sample-consumer 174 | image: aeraki/dubbo-sample-consumer 175 | env: 176 | - name: mode 177 | value: demo 178 | ports: 179 | - containerPort: 9009 180 | -------------------------------------------------------------------------------- /demo/k8s/zk/dubbo2istio.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dubbo2istio 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | labels: 11 | app: dubbo2istio 12 | name: dubbo2istio 13 | rules: 14 | - apiGroups: 15 | - networking.istio.io 16 | resources: 17 | - serviceentries 18 | verbs: 19 | - get 20 | - watch 21 | - list 22 | - update 23 | - patch 24 | - create 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | labels: 31 | app: dubbo2istio 32 | name: dubbo2istio 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: dubbo2istio 37 | subjects: 38 | - kind: ServiceAccount 39 | name: dubbo2istio 40 | namespace: meta-dubbo 41 | --- 42 | apiVersion: apps/v1 43 | kind: Deployment 44 | metadata: 45 | name: dubbo2istio 46 | labels: 47 | app: dubbo2istio 48 | spec: 49 | selector: 50 | matchLabels: 51 | app: dubbo2istio 52 | replicas: 1 53 | template: 54 | metadata: 55 | annotations: 56 | sidecar.istio.io/inject: "false" 57 | labels: 58 | app: dubbo2istio 59 | spec: 60 | serviceAccountName: dubbo2istio 61 | containers: 62 | - name: dubbo2istio 63 | image: "aeraki/dubbo2istio" 64 | imagePullPolicy: Always 65 | env: 66 | - name: REGISTRY_ADDR 67 | value: "zookeeper:2181" 68 | - name: REGISTRY_TYPE 69 | value: "zookeeper" 70 | -------------------------------------------------------------------------------- /demo/k8s/zk/zookeeper.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: zookeeper 6 | labels: 7 | app: zookeeper 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: zookeeper 12 | replicas: 1 13 | template: 14 | metadata: 15 | annotations: 16 | sidecar.istio.io/inject: "false" 17 | labels: 18 | app: zookeeper 19 | spec: 20 | containers: 21 | - name: zookeeper 22 | image: zookeeper:3.4 23 | ports: 24 | - containerPort: 2181 25 | --- 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: zookeeper 30 | spec: 31 | selector: 32 | app: zookeeper 33 | ports: 34 | - name: tcp 35 | port: 2181 36 | protocol: TCP 37 | targetPort: 2181 -------------------------------------------------------------------------------- /demo/traffic-rules/destinationrule.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Aeraki Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | --- 16 | apiVersion: networking.istio.io/v1alpha3 17 | kind: DestinationRule 18 | metadata: 19 | name: dubbo-sample-provider 20 | namespace: meta-dubbo 21 | spec: 22 | host: org.apache.dubbo.samples.basic.api.demoservice 23 | subsets: 24 | - name: v1 25 | labels: 26 | version: v1 27 | - name: v2 28 | labels: 29 | version: v2 30 | -------------------------------------------------------------------------------- /demo/traffic-rules/metarouter-v1.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Aeraki Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | --- 16 | apiVersion: metaprotocol.aeraki.io/v1alpha1 17 | kind: MetaRouter 18 | metadata: 19 | name: test-metaprotocol-dubbo-route 20 | namespace: meta-dubbo 21 | spec: 22 | hosts: 23 | - org.apache.dubbo.samples.basic.api.demoservice 24 | routes: 25 | - name: v1 26 | match: 27 | attributes: 28 | interface: 29 | exact: org.apache.dubbo.samples.basic.api.DemoService 30 | method: 31 | exact: sayHello 32 | foo: 33 | exact: bar 34 | route: 35 | - destination: 36 | host: org.apache.dubbo.samples.basic.api.demoservice 37 | subset: v1 38 | -------------------------------------------------------------------------------- /demo/traffic-rules/metarouter-v2.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Aeraki Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | --- 16 | apiVersion: metaprotocol.aeraki.io/v1alpha1 17 | kind: MetaRouter 18 | metadata: 19 | name: test-metaprotocol-dubbo-route 20 | namespace: meta-dubbo 21 | spec: 22 | hosts: 23 | - org.apache.dubbo.samples.basic.api.demoservice 24 | routes: 25 | - name: v2 26 | match: 27 | attributes: 28 | interface: 29 | exact: org.apache.dubbo.samples.basic.api.DemoService 30 | method: 31 | exact: sayHello 32 | foo: 33 | exact: bar 34 | route: 35 | - destination: 36 | host: org.apache.dubbo.samples.basic.api.demoservice 37 | subset: v2 38 | -------------------------------------------------------------------------------- /demo/traffic-rules/traffic-split.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Aeraki Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | --- 16 | apiVersion: metaprotocol.aeraki.io/v1alpha1 17 | kind: MetaRouter 18 | metadata: 19 | name: test-metaprotocol-dubbo-route 20 | namespace: meta-dubbo 21 | spec: 22 | hosts: 23 | - org.apache.dubbo.samples.basic.api.demoservice 24 | routes: 25 | - name: traffic-split 26 | match: 27 | attributes: 28 | interface: 29 | exact: org.apache.dubbo.samples.basic.api.DemoService 30 | method: 31 | exact: sayHello 32 | foo: 33 | exact: bar 34 | route: 35 | - destination: 36 | host: org.apache.dubbo.samples.basic.api.demoservice 37 | subset: v1 38 | weight: 20 39 | - destination: 40 | host: org.apache.dubbo.samples.basic.api.demoservice 41 | subset: v2 42 | weight: 80 43 | -------------------------------------------------------------------------------- /doc/dubbo2istio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeraki-mesh/dubbo2istio/8729a8871800808cfba7e294a56e1e4adf781ecf/doc/dubbo2istio.png -------------------------------------------------------------------------------- /doc/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aeraki-mesh/dubbo2istio/8729a8871800808cfba7e294a56e1e4adf781ecf/doc/grafana.png -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.13.5 2 | 3 | ENV REGISTRY_ADDR="" 4 | ENV REGISTRY_NAME="default" 5 | ENV REGISTRY_TYPE="zookeeper" 6 | 7 | COPY dubbo2istio /usr/local/bin/ 8 | ENTRYPOINT /usr/local/bin/dubbo2istio -type=$REGISTRY_TYPE -name=$REGISTRY_NAME -addr=$REGISTRY_ADDR -------------------------------------------------------------------------------- /dubbo2istio.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dubbo2istio 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | labels: 11 | app: dubbo2istio 12 | name: dubbo2istio 13 | rules: 14 | - apiGroups: 15 | - networking.istio.io 16 | resources: 17 | - serviceentries 18 | verbs: 19 | - get 20 | - watch 21 | - list 22 | - update 23 | - patch 24 | - create 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | labels: 31 | app: dubbo2istio 32 | name: dubbo2istio 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: dubbo2istio 37 | subjects: 38 | - kind: ServiceAccount 39 | name: dubbo2istio 40 | namespace: meta-dubbo 41 | --- 42 | apiVersion: apps/v1 43 | kind: Deployment 44 | metadata: 45 | name: dubbo2istio 46 | labels: 47 | app: dubbo2istio 48 | spec: 49 | selector: 50 | matchLabels: 51 | app: dubbo2istio 52 | replicas: 1 53 | template: 54 | metadata: 55 | labels: 56 | app: dubbo2istio 57 | spec: 58 | serviceAccountName: dubbo2istio 59 | containers: 60 | - name: dubbo2istio 61 | image: aeraki/dubbo2istio 62 | imagePullPolicy: Never # Set ImagePullPolicy to Never to enforce MiniKube use local image 63 | env: 64 | - name: REGISTRY_ADDR 65 | value: "zookeeper:2181" 66 | - name: REGISTRY_TYPE 67 | value: "zookeeper" 68 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aeraki-mesh/dubbo2istio 2 | 3 | go 1.14 4 | 5 | replace github.com/nacos-group/nacos-sdk-go => github.com/aeraki-mesh/nacos-sdk-go v0.0.0-20210611053344-ca0530b4c111 6 | 7 | require ( 8 | github.com/go-zookeeper/zk v1.0.2 9 | github.com/google/go-github v17.0.0+incompatible 10 | github.com/hashicorp/go-multierror v1.1.1 11 | github.com/nacos-group/nacos-sdk-go v1.0.8 12 | github.com/pkg/errors v0.9.1 13 | github.com/zhaohuabing/debounce v1.0.0 14 | go.etcd.io/etcd/api/v3 v3.5.0 15 | go.etcd.io/etcd/client/v3 v3.5.0 16 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d 17 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 18 | golang.org/x/tools v0.1.7 // indirect 19 | google.golang.org/grpc v1.38.0 20 | istio.io/api v0.0.0-20210601145914-9a4239731e79 21 | istio.io/client-go v0.0.0-20210601151459-89ee09f12704 22 | istio.io/istio v0.0.0-20210603041206-aa439f6e4772 23 | istio.io/pkg v0.0.0-20210528151021-2059ed14a0e6 24 | k8s.io/apimachinery v0.21.1 25 | sigs.k8s.io/controller-runtime v0.9.0-beta.5 26 | ) 27 | -------------------------------------------------------------------------------- /pkg/dubbo/common/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | const ( 18 | dubboPortName = "tcp-metaprotocol-dubbo" 19 | dubbo2istio = "dubbo2istio" 20 | registryNameLabel = "registryName" 21 | defaultServiceAccount = "default" 22 | aerakiFieldManager = "aeraki" // aerakiFieldManager is the FileldManager for Aeraki CRDs 23 | ) 24 | -------------------------------------------------------------------------------- /pkg/dubbo/common/conversion.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "net/url" 19 | "regexp" 20 | "strings" 21 | 22 | "istio.io/client-go/pkg/apis/networking/v1alpha3" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | 25 | istio "istio.io/api/networking/v1alpha3" 26 | "istio.io/pkg/log" 27 | ) 28 | 29 | var labelRegexp *regexp.Regexp 30 | 31 | func init() { 32 | labelRegexp = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$") 33 | } 34 | 35 | // ConvertServiceEntry converts dubbo provider znode to a service entry 36 | func ConvertServiceEntry(registryName string, 37 | instances []DubboServiceInstance) map[string]*v1alpha3.ServiceEntry { 38 | serviceEntries := make(map[string]*v1alpha3.ServiceEntry) 39 | 40 | for _, instance := range instances { 41 | dubboAttributes := instance.Metadata 42 | 43 | // interface+group+version+nacosGroup+nacosNS is the primary key of a dubbo service 44 | host := constructServiceHost(instance.Interface, dubboAttributes["version"], dubboAttributes["group"], 45 | instance.Group, instance.Namespace) 46 | 47 | serviceAccount := dubboAttributes["aeraki_meta_app_service_account"] 48 | if serviceAccount == "" { 49 | serviceAccount = defaultServiceAccount 50 | } 51 | 52 | ns := dubboAttributes["aeraki_meta_app_namespace"] 53 | if ns == "" { 54 | log.Errorf("can't find aeraki_meta_app_namespace parameter, ignore provider %v, ", dubboAttributes) 55 | continue 56 | } 57 | // All the providers of a dubbo service should be deployed in the same namespace 58 | if se, exist := serviceEntries[host]; exist { 59 | if ns != se.Namespace { 60 | log.Errorf("found provider in multiple namespaces: %s %s, ignore provider %v", se.Namespace, ns, 61 | dubboAttributes) 62 | continue 63 | } 64 | } 65 | 66 | // We assume that the port of all the provider instances should be the same. Is this a reasonable assumption? 67 | if se, exist := serviceEntries[host]; exist { 68 | if instance.Port != se.Spec.Ports[0].Number { 69 | log.Errorf("found multiple ports for service %s : %v %v, ignore provider %v", host, 70 | se.Spec.Ports[0].Number, instance.Port, dubboAttributes) 71 | continue 72 | } 73 | } 74 | 75 | // What is this for? Authorization? 76 | unDecodeSelector := dubboAttributes["aeraki_meta_workload_selector"] 77 | selector, err := url.QueryUnescape(unDecodeSelector) 78 | if err != nil { 79 | log.Errorf("QueryUnescape aeraki_meta_workload_selector parameter for provider %v, get err %v", dubboAttributes, err) 80 | } 81 | if selector == "" { 82 | log.Errorf("can't find aeraki_meta_workload_selector parameter for provider %v, ", dubboAttributes) 83 | } 84 | 85 | if se, exist := serviceEntries[host]; exist { 86 | workloadSelector := se.Annotations["workloadSelector"] 87 | if selector != workloadSelector { 88 | log.Errorf("found provider %s with different workload selectors: %v %s", dubboAttributes, 89 | workloadSelector, 90 | selector) 91 | } 92 | } 93 | 94 | locality := strings.ReplaceAll(dubboAttributes["aeraki_meta_locality"], "%2F", "/") 95 | 96 | labels := dubboAttributes 97 | delete(labels, "service") 98 | delete(labels, "ip") 99 | delete(labels, "port") 100 | delete(labels, "aeraki_meta_app_service_account") 101 | delete(labels, "aeraki_meta_app_namespace") 102 | delete(labels, "aeraki_meta_workload_selector") 103 | delete(labels, "aeraki_meta_locality") 104 | 105 | version := dubboAttributes["aeraki_meta_app_version"] 106 | if version != "" { 107 | labels["version"] = version 108 | } 109 | 110 | delete(labels, "aeraki_meta_app_version") 111 | for key, value := range labels { 112 | value = strings.ReplaceAll(value, ",", "-") 113 | labels[key] = value 114 | if isInvalidLabel(key, value) { 115 | delete(labels, key) 116 | log.Infof("drop invalid label: key %s, value: %s", key, value) 117 | } 118 | } 119 | // to distinguish endpoints from different zk clusters 120 | labels[registryNameLabel] = registryName 121 | serviceEntry, exist := serviceEntries[host] 122 | if !exist { 123 | serviceEntry = constructServiceEntry(ns, host, instance.Interface, instance.Port, selector) 124 | serviceEntries[host] = serviceEntry 125 | } 126 | serviceEntry.Spec.Endpoints = append(serviceEntry.Spec.Endpoints, 127 | constructWorkloadEntry(instance.IP, serviceAccount, instance.Port, locality, labels)) 128 | } 129 | return serviceEntries 130 | } 131 | 132 | func constructServiceHost(dubboInterface, version, serviceGroup, registryGroup, registryNamespace string) string { 133 | host := dubboInterface 134 | if serviceGroup != "" { 135 | host = host + "-" + serviceGroup 136 | } 137 | version = strings.ReplaceAll(version, ".", "-") 138 | if version != "" { 139 | host = host + "-" + version 140 | } 141 | if registryGroup != "" && registryGroup != "DEFAULT_GROUP" { 142 | host = host + "." + registryGroup 143 | } 144 | if registryNamespace != "" && registryNamespace != "public" { 145 | host = host + "." + registryNamespace 146 | } 147 | return strings.ToLower(host) 148 | } 149 | 150 | func constructServiceEntry(namespace string, host string, dubboInterface string, servicePort uint32, 151 | workloadSelector string) *v1alpha3.ServiceEntry { 152 | spec := &istio.ServiceEntry{ 153 | Hosts: []string{host}, 154 | Ports: []*istio.Port{convertPort(servicePort)}, 155 | Resolution: istio.ServiceEntry_STATIC, 156 | Location: istio.ServiceEntry_MESH_INTERNAL, 157 | Endpoints: make([]*istio.WorkloadEntry, 0), 158 | } 159 | 160 | serviceEntry := &v1alpha3.ServiceEntry{ 161 | ObjectMeta: v1.ObjectMeta{ 162 | Name: ConstructServiceEntryName(host), 163 | Namespace: namespace, 164 | Labels: map[string]string{ 165 | "manager": aerakiFieldManager, 166 | "registry": dubbo2istio, 167 | }, 168 | Annotations: map[string]string{ 169 | "interface": dubboInterface, 170 | "workloadSelector": workloadSelector, 171 | }, 172 | }, 173 | Spec: *spec, 174 | } 175 | return serviceEntry 176 | } 177 | 178 | func constructWorkloadEntry(ip string, serviceAccount string, port uint32, locality string, 179 | labels map[string]string) *istio.WorkloadEntry { 180 | return &istio.WorkloadEntry{ 181 | Address: ip, 182 | Ports: map[string]uint32{dubboPortName: port}, 183 | ServiceAccount: serviceAccount, 184 | Locality: locality, 185 | Labels: labels, 186 | } 187 | } 188 | 189 | func isInvalidLabel(key string, value string) bool { 190 | return !labelRegexp.MatchString(key) || !labelRegexp.MatchString(value) || len(key) > 63 || len(value) > 63 191 | } 192 | 193 | func convertPort(port uint32) *istio.Port { 194 | return &istio.Port{ 195 | Number: port, 196 | Protocol: "tcp", 197 | Name: dubboPortName, 198 | TargetPort: port, 199 | } 200 | } 201 | 202 | // ConstructServiceEntryName constructs the service entry name for a given dubbo service 203 | func ConstructServiceEntryName(service string) string { 204 | validDNSName := strings.ReplaceAll(strings.ToLower(service), ".", "-") 205 | return aerakiFieldManager + "-" + validDNSName 206 | } 207 | -------------------------------------------------------------------------------- /pkg/dubbo/common/conversion_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "net/url" 19 | "testing" 20 | ) 21 | 22 | func Test_isValidLabel(t *testing.T) { 23 | tests := []struct { 24 | key string 25 | value string 26 | want bool 27 | }{ 28 | { 29 | key: "method", 30 | value: "testVoid%2CsayHello", 31 | want: true, 32 | }, 33 | { 34 | key: "interface", 35 | value: "org.apache.dubbo.samples.basic.api.DemoService", 36 | want: false, 37 | }, 38 | { 39 | key: "interface", 40 | value: "org.apache_dubbo-samples.basic.api.DemoService", 41 | want: false, 42 | }, 43 | } 44 | for _, tt := range tests { 45 | t.Run("", func(t *testing.T) { 46 | if got := isInvalidLabel(tt.key, tt.value); got != tt.want { 47 | t.Errorf("isValidLabel() = %v, want %v", got, tt.want) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func Test_iQueryUnescape(t *testing.T) { 54 | tests := []struct { 55 | testString string 56 | wantString string 57 | }{ 58 | { 59 | testString: "app%3A+dubbo-consumer", 60 | wantString: "app: dubbo-consumer", 61 | }, 62 | { 63 | testString: "app: dubbo-consumer", 64 | wantString: "app: dubbo-consumer", 65 | }, 66 | } 67 | for _, tt := range tests { 68 | t.Run("", func(t *testing.T) { 69 | if got, _ := url.QueryUnescape(tt.testString); got != tt.wantString { 70 | t.Errorf("QueryUnescape() = %v, want %v", got, tt.wantString) 71 | } 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pkg/dubbo/common/model.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | // DubboServiceInstance contains the information of a dubbo service instance 18 | type DubboServiceInstance struct { 19 | IP string `json:"ip"` 20 | Port uint32 `json:"port"` 21 | Interface string `json:"interface"` 22 | Namespace string `json:"namespace"` 23 | Group string `json:"group"` 24 | Metadata map[string]string `json:"metadata"` 25 | } 26 | -------------------------------------------------------------------------------- /pkg/dubbo/common/serviceentry.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "sync" 19 | 20 | istioclient "istio.io/client-go/pkg/clientset/versioned" 21 | 22 | "istio.io/pkg/log" 23 | "k8s.io/apimachinery/pkg/api/errors" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | 26 | "context" 27 | "encoding/json" 28 | 29 | "istio.io/client-go/pkg/apis/networking/v1alpha3" 30 | ) 31 | 32 | const ( 33 | // the maximum retries if failed to sync dubbo services to Istio 34 | maxRetries = 10 35 | ) 36 | 37 | var serviceEntryNS = make(map[string]string) //key service entry name, value namespace 38 | var serviceEntryMutex sync.Mutex 39 | 40 | // SyncServices2IstioUntilMaxRetries will try to synchronize service entries to the Istio until max retry number 41 | func SyncServices2IstioUntilMaxRetries(new *v1alpha3.ServiceEntry, registryName string, ic *istioclient.Clientset) { 42 | serviceEntryMutex.Lock() 43 | defer serviceEntryMutex.Unlock() 44 | 45 | err := syncService2Istio(new, registryName, ic) 46 | retries := 0 47 | for err != nil { 48 | if isRetryableError(err) && retries < maxRetries { 49 | log.Errorf("Failed to synchronize dubbo services to Istio, error: %v, retrying %v ...", err, retries) 50 | err = syncService2Istio(new, registryName, ic) 51 | retries++ 52 | } else { 53 | log.Errorf("Failed to synchronize dubbo services to Istio: %v", err) 54 | err = nil 55 | } 56 | } 57 | } 58 | 59 | func syncService2Istio(new *v1alpha3.ServiceEntry, registryName string, ic *istioclient.Clientset) error { 60 | // delete old service entry if multiple service entries found in different namespaces. 61 | // Aeraki doesn't support deploying providers of the same dubbo interface in multiple namespaces because interface 62 | // is used as the global dns name for dubbo service across the whole mesh 63 | if oldNS, exist := serviceEntryNS[new.Name]; exist { 64 | if oldNS != new.Namespace { 65 | log.Errorf("found service entry %s in two namespaces : %s %s ,delete the older one %s/%s", new.Name, oldNS, 66 | new.Namespace, oldNS, new.Name) 67 | if err := ic.NetworkingV1alpha3().ServiceEntries(oldNS).Delete(context.TODO(), new.Name, 68 | metav1.DeleteOptions{}); err != nil { 69 | if isRealError(err) { 70 | log.Errorf("failed to delete service entry: %s/%s", oldNS, new.Name) 71 | } 72 | } 73 | } 74 | } 75 | 76 | existingServiceEntry, err := ic.NetworkingV1alpha3().ServiceEntries(new.Namespace).Get(context.TODO(), new.Name, 77 | metav1.GetOptions{}, 78 | ) 79 | 80 | if isRealError(err) { 81 | return err 82 | } else if isNotFound(err) { 83 | return createServiceEntry(new, ic) 84 | } else { 85 | mergeServiceEntryEndpoints(registryName, new, existingServiceEntry) 86 | return updateServiceEntry(new, existingServiceEntry, ic) 87 | } 88 | } 89 | 90 | func createServiceEntry(serviceEntry *v1alpha3.ServiceEntry, ic *istioclient.Clientset) error { 91 | _, err := ic.NetworkingV1alpha3().ServiceEntries(serviceEntry.Namespace).Create(context.TODO(), serviceEntry, 92 | metav1.CreateOptions{FieldManager: aerakiFieldManager}) 93 | if err == nil { 94 | serviceEntryNS[serviceEntry.Name] = serviceEntry.Namespace 95 | log.Infof("service entry %s has been created: %s", serviceEntry.Name, struct2JSON(serviceEntry)) 96 | } 97 | return err 98 | } 99 | 100 | func updateServiceEntry(new *v1alpha3.ServiceEntry, 101 | old *v1alpha3.ServiceEntry, ic *istioclient.Clientset) error { 102 | new.Spec.Ports = old.Spec.Ports 103 | new.ResourceVersion = old.ResourceVersion 104 | _, err := ic.NetworkingV1alpha3().ServiceEntries(new.Namespace).Update(context.TODO(), new, 105 | metav1.UpdateOptions{FieldManager: aerakiFieldManager}) 106 | if err == nil { 107 | log.Infof("service entry %s has been updated: %s", new.Name, struct2JSON(new)) 108 | } 109 | return err 110 | } 111 | 112 | func isRealError(err error) bool { 113 | return err != nil && !errors.IsNotFound(err) 114 | } 115 | 116 | func isRetryableError(err error) bool { 117 | return errors.IsInternalError(err) || errors.IsResourceExpired(err) || errors.IsServerTimeout(err) || 118 | errors.IsServiceUnavailable(err) || errors.IsTimeout(err) || errors.IsTooManyRequests(err) || 119 | errors.ReasonForError(err) == metav1.StatusReasonUnknown 120 | } 121 | 122 | func isNotFound(err error) bool { 123 | return err != nil && errors.IsNotFound(err) 124 | } 125 | 126 | func struct2JSON(ojb interface{}) interface{} { 127 | b, err := json.Marshal(ojb) 128 | if err != nil { 129 | return ojb 130 | } 131 | return string(b) 132 | } 133 | 134 | func mergeServiceEntryEndpoints(registryName string, new *v1alpha3.ServiceEntry, old *v1alpha3.ServiceEntry) { 135 | if old == nil || old.Spec.WorkloadSelector != nil { 136 | return 137 | } 138 | endpoints := new.Spec.Endpoints 139 | for _, ep := range old.Spec.Endpoints { 140 | // keep endpoints synchronized by other zk and delete old endpoints synchronized by zk configured in zkAddr 141 | if ep.Labels[registryNameLabel] != registryName { 142 | endpoints = append(endpoints, ep) 143 | } 144 | } 145 | new.Spec.Endpoints = endpoints 146 | } 147 | -------------------------------------------------------------------------------- /pkg/dubbo/common/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "strings" 21 | 22 | "github.com/nacos-group/nacos-sdk-go/clients" 23 | "github.com/nacos-group/nacos-sdk-go/clients/naming_client" 24 | "github.com/nacos-group/nacos-sdk-go/common/constant" 25 | "github.com/nacos-group/nacos-sdk-go/vo" 26 | ) 27 | 28 | // CopyMap deep copy a map 29 | func CopyMap(m map[string]string) map[string]string { 30 | cp := make(map[string]string) 31 | for k, v := range m { 32 | cp[k] = v 33 | } 34 | return cp 35 | } 36 | 37 | // NewNacosClisent creates a Nacos client 38 | func NewNacosClisent(addr, namespace string) (naming_client.INamingClient, error) { 39 | host, port, err := parseNacosURL(addr) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | sc := []constant.ServerConfig{ 45 | *constant.NewServerConfig(host, port), 46 | } 47 | 48 | cc := *constant.NewClientConfig( 49 | constant.WithTimeoutMs(5000), 50 | constant.WithNotLoadCacheAtStart(true), 51 | constant.WithLogDir("/tmp/nacos/log"), 52 | constant.WithCacheDir("/tmp/nacos/cache"), 53 | constant.WithRotateTime("1h"), 54 | constant.WithMaxAge(3), 55 | constant.WithLogLevel("info"), 56 | constant.WithNamespaceId(namespace), 57 | ) 58 | 59 | return clients.NewNamingClient( 60 | vo.NacosClientParam{ 61 | ClientConfig: &cc, 62 | ServerConfigs: sc, 63 | }, 64 | ) 65 | } 66 | 67 | func parseNacosURL(ncAddr string) (string, uint64, error) { 68 | strArray := strings.Split(strings.TrimSpace(ncAddr), ":") 69 | if len(strArray) < 2 { 70 | return "", 0, fmt.Errorf("invalid nacos address: %s, "+ 71 | "please specify a valid nacos address like \"nacos:8848\"", ncAddr) 72 | } 73 | port, err := strconv.Atoi(strArray[1]) 74 | if err != nil { 75 | return "", 0, fmt.Errorf("invalid nacos address: %s, "+ 76 | "please specify a valid nacos address like \"nacos:8848\"", ncAddr) 77 | } 78 | host := strArray[0] 79 | return host, uint64(port), nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/dubbo/common/utils_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "testing" 4 | 5 | func Test_parseNacosURL(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | addr string 9 | host string 10 | port uint64 11 | wantErr bool 12 | }{ 13 | { 14 | name: "valid addr", 15 | addr: "nacos.dubbo:8848", 16 | host: "nacos.dubbo", 17 | port: 8848, 18 | wantErr: false, 19 | }, 20 | { 21 | name: "invalid addr", 22 | addr: "nacos.dubbo", 23 | host: "", 24 | port: 0, 25 | wantErr: true, 26 | }, 27 | } 28 | for _, tt := range tests { 29 | t.Run(tt.name, func(t *testing.T) { 30 | host, port, err := parseNacosURL(tt.addr) 31 | if (err != nil) != tt.wantErr { 32 | t.Errorf("parseNacosURL() error = %v, wantErr %v", err, tt.wantErr) 33 | return 34 | } 35 | if host != tt.host { 36 | t.Errorf("parseNacosURL() got = %v, want %v", host, tt.host) 37 | } 38 | if port != tt.port { 39 | t.Errorf("parseNacosURL() got1 = %v, want %v", port, tt.port) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/dubbo/etcd/connHandler.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package etcd 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "github.com/zhaohuabing/debounce" 22 | "google.golang.org/grpc/stats" 23 | 24 | "istio.io/pkg/log" 25 | ) 26 | 27 | type connHandler struct { 28 | stats.Handler 29 | *debounce.Debouncer 30 | } 31 | 32 | func newConnHandler(callback func(), stop <-chan struct{}) *connHandler { 33 | handler := &connHandler{ 34 | Debouncer: debounce.New(5*time.Second, 10*time.Second, callback, stop), 35 | } 36 | return handler 37 | } 38 | func (connHandler) TagRPC(context context.Context, tagInfo *stats.RPCTagInfo) context.Context { 39 | return context 40 | } 41 | 42 | // HandleRPC processes the RPC stats. 43 | func (connHandler) HandleRPC(context.Context, stats.RPCStats) {} 44 | 45 | func (connHandler) TagConn(context context.Context, info *stats.ConnTagInfo) context.Context { 46 | return context 47 | } 48 | 49 | // HandleConn processes the Conn stats. 50 | func (c connHandler) HandleConn(context context.Context, connStats stats.ConnStats) { 51 | switch connStats.(type) { 52 | case *stats.ConnBegin: 53 | log.Infof("ConnBegin") 54 | c.Bounce() 55 | case *stats.ConnEnd: 56 | log.Infof("ConnEnd") 57 | c.Cancel() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/dubbo/etcd/controller.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package etcd 16 | 17 | import ( 18 | "context" 19 | "strings" 20 | "sync" 21 | "time" 22 | 23 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/common" 24 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/zk/model" 25 | 26 | clientv3 "go.etcd.io/etcd/client/v3" 27 | "google.golang.org/grpc" 28 | "google.golang.org/grpc/backoff" 29 | istioclient "istio.io/client-go/pkg/clientset/versioned" 30 | "istio.io/pkg/log" 31 | ) 32 | 33 | var serviceMutex sync.Mutex 34 | 35 | // Controller creates a etcd controller 36 | type Controller struct { 37 | registryName string // registryName is the globally unique name of a dubbo registry 38 | addr string 39 | isClient *istioclient.Clientset 40 | etcdClient *clientv3.Client 41 | watcher *watcher 42 | } 43 | 44 | // NewController creates a etcd controller 45 | func NewController(registryName string, addr string, client *istioclient.Clientset) *Controller { 46 | return &Controller{ 47 | registryName: registryName, 48 | addr: addr, 49 | isClient: client, 50 | } 51 | } 52 | 53 | // Run until a signal is received, this function won't block 54 | func (c *Controller) Run(stop <-chan struct{}) { 55 | hosts := strings.Split(c.addr, ",") 56 | c.etcdClient = c.connectEtcdUntilSuccess(hosts, stop) 57 | } 58 | 59 | func getServiceFromEvent(key string) string { 60 | if strings.Contains(key, "/providers/") { 61 | strArray := strings.Split(key, "/") 62 | if len(strArray) >= 5 && strArray[3] == "providers" { 63 | service := strArray[2] 64 | return service 65 | } 66 | } 67 | return "" 68 | } 69 | func (c *Controller) connectEtcdUntilSuccess(hosts []string, stop <-chan struct{}) *clientv3.Client { 70 | cli, err := c.createEtcdClient(hosts, stop) 71 | //Keep trying to connect to etcd until succeed 72 | for err != nil { 73 | log.Errorf("failed to connect to Etcd %s ,retrying : %v", hosts, err) 74 | time.Sleep(time.Second) 75 | cli, err = c.createEtcdClient(hosts, stop) 76 | } 77 | log.Infof("connected to etcd: %s", hosts) 78 | return cli 79 | } 80 | 81 | func (c *Controller) createEtcdClient(hosts []string, stop <-chan struct{}) (*clientv3.Client, error) { 82 | return clientv3.New(clientv3.Config{ 83 | Endpoints: hosts, 84 | DialTimeout: 5 * time.Second, 85 | DialKeepAliveTime: 5 * time.Second, 86 | DialKeepAliveTimeout: 5 * time.Second, 87 | DialOptions: []grpc.DialOption{grpc.WithBlock(), 88 | grpc.WithConnectParams(grpc.ConnectParams{Backoff: backoff.Config{ 89 | BaseDelay: 1.0 * time.Second, 90 | Multiplier: 1.6, 91 | Jitter: 0.2, 92 | MaxDelay: 30 * time.Second, 93 | }}), grpc. 94 | WithStatsHandler(newConnHandler(c.syncAndWatch, stop))}, 95 | }) 96 | } 97 | 98 | func (c *Controller) syncAndWatch() { 99 | if c.watcher != nil { 100 | c.watcher.stop() 101 | } 102 | c.watcher = newWatcher(c.registryName, c.etcdClient, c.isClient) 103 | go c.watcher.watchService() 104 | 105 | serviceMutex.Lock() 106 | defer serviceMutex.Unlock() 107 | c.syncAllService() 108 | } 109 | 110 | func (c *Controller) syncAllService() { 111 | log.Info("synchronize all dubbo services from etcd.") 112 | key := "/dubbo/" 113 | if response, err := c.etcdClient.Get(context.TODO(), key, clientv3.WithPrefix()); err != nil { 114 | log.Errorf("failed to get all services from Etcd") 115 | } else { 116 | services := parseGetResponse(response) 117 | for service, providers := range services { 118 | log.Infof("synchronize dubbo service: %s to Istio", service) 119 | syncServices2Istio(c.registryName, c.isClient, service, providers) 120 | } 121 | } 122 | } 123 | 124 | func syncServices2Istio(registryName string, isClient *istioclient.Clientset, service string, providers []string) { 125 | if len(providers) == 0 { 126 | log.Warnf("service %s has no providers, ignore synchronize job", service) 127 | return 128 | } 129 | serviceEntries, err := model.ConvertServiceEntry(registryName, providers) 130 | if err != nil { 131 | log.Errorf("failed to synchronize dubbo services to Istio: %v", err) 132 | } 133 | for _, new := range serviceEntries { 134 | common.SyncServices2IstioUntilMaxRetries(new, registryName, isClient) 135 | } 136 | } 137 | 138 | func parseGetResponse(response *clientv3.GetResponse) map[string][]string { 139 | services := make(map[string][]string) 140 | for _, kv := range response.Kvs { 141 | key := string(kv.Key) 142 | array := strings.Split(key, "/") 143 | if len(array) == 5 && array[1] == "dubbo" && array[3] == "providers" { 144 | service := array[2] 145 | providers := services[service] 146 | if providers == nil { 147 | providers = []string{} 148 | } 149 | providers = append(providers, array[4]) 150 | services[service] = providers 151 | } 152 | } 153 | return services 154 | } 155 | -------------------------------------------------------------------------------- /pkg/dubbo/etcd/controller_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "go.etcd.io/etcd/api/v3/mvccpb" 8 | clientv3 "go.etcd.io/etcd/client/v3" 9 | ) 10 | 11 | func Test_getServiceFromEvent(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | key string 15 | want string 16 | }{{ 17 | name: "test", 18 | key: "/dubbo/org.apache.dubbo.samples.basic.api.ComplexService/providers/dubbo%3A%2F%2F172.20.0.69%3A20880%2Forg.apache.dubbo.samples.basic.api.ComplexService%3Faeraki_meta_app_namespace%3Ddubbo%26aeraki_meta_app_service_account%3Ddefault%26aeraki_meta_app_version%3Dv1%26aeraki_meta_locality%3Dbj%2F800002%26aeraki_meta_workload_selector%3Ddubbo-sample-provider%26anyhost%3Dtrue%26application%3Ddubbo-sample-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Dproduct%26interface%3Dorg.apache.dubbo.samples.basic.api.ComplexService%26methods%3DtestVoid%2CsayHello%26pid%3D7%26release%3D1.0-SNAPSHOT%26revision%3D1.0-SNAPSHOT%26service.name%3DServiceBean%3Aproduct%2Forg.apache.dubbo.samples.basic.api.ComplexService%3A2.0.0%26service_group%3Duser%26side%3Dprovider%26timestamp%3D1623819379414%26version%3D2.0.0", 19 | want: "org.apache.dubbo.samples.basic.api.ComplexService", 20 | }} 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | if got := getServiceFromEvent(tt.key); got != tt.want { 24 | t.Errorf("getServiceFromEvent() = %v, want %v", got, tt.want) 25 | } 26 | }) 27 | } 28 | } 29 | 30 | func Test_parseGetResponse(t *testing.T) { 31 | tests := []struct { 32 | name string 33 | response *clientv3.GetResponse 34 | want map[string][]string 35 | }{ 36 | { 37 | name: "testParseGetResponse", 38 | response: &clientv3.GetResponse{ 39 | Kvs: []*mvccpb.KeyValue{ 40 | { 41 | Key: []byte("/dubbo/org.apache.dubbo.samples.basic.api.DemoService/providers/dubbo%3A%2F%2F172.20.0.29%3A20880%2Forg.apache.dubbo.samples.basic.api.DemoService%3Faeraki_meta_app_namespace%3Ddubbo%26aeraki_meta_app_service_account%3Ddefault%26aeraki_meta_app_version%3Dv2%26aeraki_meta_locality%3Dbj%2F800005%26aeraki_meta_workload_selector%3Ddubbo-sample-provider%26anyhost%3Dtrue%26application%3Ddubbo-sample-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.basic.api.DemoService%26methods%3DtestVoid%2CsayHello%26pid%3D6%26release%3D1.0-SNAPSHOT%26revision%3D1.0-SNAPSHOT%26service.name%3DServiceBean%3A%2Forg.apache.dubbo.samples.basic.api.DemoService%26service_group%3Dbatchjob%26side%3Dprovider%26timestamp%3D1624184681126"), 42 | }, 43 | { 44 | Key: []byte("/dubbo/org.apache.dubbo.samples.basic.api.DemoService/providers/dubbo%3A%2F%2F172.20.0.28%3A20880%2Forg.apache.dubbo.samples.basic.api.DemoService%3Faeraki_meta_app_namespace%3Ddubbo%26aeraki_meta_app_service_account%3Ddefault%26aeraki_meta_app_version%3Dv2%26aeraki_meta_locality%3Dbj%2F800005%26aeraki_meta_workload_selector%3Ddubbo-sample-provider%26anyhost%3Dtrue%26application%3Ddubbo-sample-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.basic.api.DemoService%26methods%3DtestVoid%2CsayHello%26pid%3D6%26release%3D1.0-SNAPSHOT%26revision%3D1.0-SNAPSHOT%26service.name%3DServiceBean%3A%2Forg.apache.dubbo.samples.basic.api.DemoService%26service_group%3Dbatchjob%26side%3Dprovider%26timestamp%3D1624184681892"), 45 | }, 46 | }, 47 | }, 48 | want: map[string][]string{"org.apache.dubbo.samples.basic.api.DemoService": { 49 | "dubbo%3A%2F%2F172.20.0.29%3A20880%2Forg.apache.dubbo.samples.basic.api.DemoService%3Faeraki_meta_app_namespace%3Ddubbo%26aeraki_meta_app_service_account%3Ddefault%26aeraki_meta_app_version%3Dv2%26aeraki_meta_locality%3Dbj%2F800005%26aeraki_meta_workload_selector%3Ddubbo-sample-provider%26anyhost%3Dtrue%26application%3Ddubbo-sample-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.basic.api.DemoService%26methods%3DtestVoid%2CsayHello%26pid%3D6%26release%3D1.0-SNAPSHOT%26revision%3D1.0-SNAPSHOT%26service.name%3DServiceBean%3A%2Forg.apache.dubbo.samples.basic.api.DemoService%26service_group%3Dbatchjob%26side%3Dprovider%26timestamp%3D1624184681126", 50 | "dubbo%3A%2F%2F172.20.0.28%3A20880%2Forg.apache.dubbo.samples.basic.api.DemoService%3Faeraki_meta_app_namespace%3Ddubbo%26aeraki_meta_app_service_account%3Ddefault%26aeraki_meta_app_version%3Dv2%26aeraki_meta_locality%3Dbj%2F800005%26aeraki_meta_workload_selector%3Ddubbo-sample-provider%26anyhost%3Dtrue%26application%3Ddubbo-sample-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.basic.api.DemoService%26methods%3DtestVoid%2CsayHello%26pid%3D6%26release%3D1.0-SNAPSHOT%26revision%3D1.0-SNAPSHOT%26service.name%3DServiceBean%3A%2Forg.apache.dubbo.samples.basic.api.DemoService%26service_group%3Dbatchjob%26side%3Dprovider%26timestamp%3D1624184681892", 51 | }}, 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | if got := parseGetResponse(tt.response); !reflect.DeepEqual(got, tt.want) { 57 | t.Errorf("parseGetResponse() = %v, want %v", got, tt.want) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/dubbo/etcd/watcher.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package etcd 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "github.com/zhaohuabing/debounce" 22 | 23 | clientv3 "go.etcd.io/etcd/client/v3" 24 | istioclient "istio.io/client-go/pkg/clientset/versioned" 25 | "istio.io/pkg/log" 26 | ) 27 | 28 | const ( 29 | // debounceAfter is the delay added to events to wait after a registry event for debouncing. 30 | // This will delay the push by at least this interval, plus the time getting subsequent events. 31 | // If no change is detected the push will happen, otherwise we'll keep delaying until things settle. 32 | debounceAfter = 5 * time.Second 33 | 34 | // debounceMax is the maximum time to wait for events while debouncing. 35 | // Defaults to 10 seconds. If events keep showing up with no break for this time, we'll trigger a push. 36 | debounceMax = 20 * time.Second 37 | ) 38 | 39 | type watcher struct { 40 | registryName string // registryName is the globally unique name of a dubbo registry 41 | isClient *istioclient.Clientset 42 | etcdClient *clientv3.Client 43 | stopChan chan struct{} 44 | } 45 | 46 | func newWatcher(registryName string, etcdClient *clientv3.Client, 47 | client *istioclient.Clientset) *watcher { 48 | return &watcher{ 49 | registryName: registryName, 50 | etcdClient: etcdClient, 51 | isClient: client, 52 | stopChan: make(chan struct{}), 53 | } 54 | } 55 | 56 | func (w *watcher) stop() { 57 | w.stopChan <- struct{}{} 58 | } 59 | 60 | func (w *watcher) watchService() { 61 | log.Info("start to watch etcd") 62 | ctx, cancel := context.WithCancel(context.Background()) 63 | watchChannel := w.etcdClient.Watch(clientv3.WithRequireLeader(ctx), "/dubbo", clientv3.WithPrefix()) 64 | changedServices := make(map[string]string) 65 | 66 | callback := func() { 67 | serviceMutex.Lock() 68 | defer serviceMutex.Unlock() 69 | log.Infof("changedEvent :%v", changedServices) 70 | for service := range changedServices { 71 | w.processChangedService(service) 72 | } 73 | } 74 | debouncer := debounce.New(debounceAfter, debounceMax, callback, w.stopChan) 75 | for { 76 | select { 77 | case response, more := <-watchChannel: 78 | if err := response.Err(); err != nil || !more { 79 | log.Errorf("failed to watch etcd: %v", err) 80 | ctx, cancel = context.WithCancel(context.Background()) 81 | watchChannel = w.etcdClient.Watch(clientv3.WithRequireLeader(ctx), "/dubbo", clientv3.WithPrefix()) 82 | } 83 | serviceMutex.Lock() 84 | for _, event := range response.Events { 85 | key := string(event.Kv.Key) 86 | if service := getServiceFromEvent(key); service != "" { 87 | changedServices[service] = service 88 | log.Infof("%s service %s", event.Type.String(), service) 89 | } 90 | } 91 | serviceMutex.Unlock() 92 | debouncer.Bounce() 93 | case <-w.stopChan: 94 | cancel() 95 | log.Infof("this etcd watcher has been closed.") 96 | return 97 | } 98 | } 99 | } 100 | 101 | func (w *watcher) processChangedService(service string) { 102 | key := "/dubbo/" + service + "/providers/" 103 | if response, err := w.etcdClient.Get(context.TODO(), key, clientv3.WithPrefix()); err != nil { 104 | log.Errorf("failed to get service providers: %s from Etcd", service) 105 | } else { 106 | providers := make([]string, 0) 107 | for _, kv := range response.Kvs { 108 | providers = append(providers, string(kv.Key)) 109 | 110 | } 111 | log.Infof("synchronize dubbo service: %s to Istio", service) 112 | syncServices2Istio(w.registryName, w.isClient, service, providers) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pkg/dubbo/nacos/controller.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nacos 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | 21 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/common" 22 | 23 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/nacos/watcher" 24 | "github.com/nacos-group/nacos-sdk-go/clients/naming_client" 25 | "github.com/zhaohuabing/debounce" 26 | istioclient "istio.io/client-go/pkg/clientset/versioned" 27 | "istio.io/pkg/log" 28 | ) 29 | 30 | const ( 31 | // debounceAfter is the delay added to events to wait after a registry event for debouncing. 32 | // This will delay the push by at least this interval, plus the time getting subsequent events. 33 | // If no change is detected the push will happen, otherwise we'll keep delaying until things settle. 34 | debounceAfter = 3 * time.Second 35 | 36 | // debounceMax is the maximum time to wait for events while debouncing. 37 | // Defaults to 10 seconds. If events keep showing up with no break for this time, we'll trigger a push. 38 | debounceMax = 10 * time.Second 39 | ) 40 | 41 | // Controller contains the runtime configuration for a Nacos controller 42 | type Controller struct { 43 | mutex sync.Mutex 44 | registryName string // registryName is the globally unique name of a dubbo registry 45 | ncAddr string 46 | ic *istioclient.Clientset 47 | nacosClient naming_client.INamingClient 48 | watchedNamespace map[string]bool 49 | serviceEntryNS map[string]string // key service entry name, value namespace 50 | eventChan chan []common.DubboServiceInstance 51 | } 52 | 53 | // NewController creates a Nacos Controller 54 | func NewController(ncName string, ncAddr string, client *istioclient.Clientset) (*Controller, error) { 55 | nacosClient, error := common.NewNacosClisent(ncAddr, "") 56 | if error != nil { 57 | return nil, error 58 | } 59 | return &Controller{ 60 | registryName: ncName, 61 | ncAddr: ncAddr, 62 | ic: client, 63 | nacosClient: nacosClient, 64 | watchedNamespace: make(map[string]bool), 65 | serviceEntryNS: make(map[string]string), 66 | eventChan: make(chan []common.DubboServiceInstance), 67 | }, nil 68 | } 69 | 70 | // Run until a signal is received, this function won't block 71 | func (c *Controller) Run(stop <-chan struct{}) { 72 | go c.watchNamespace(stop) 73 | go c.watchService(stop) 74 | } 75 | 76 | func (c *Controller) watchService(stop <-chan struct{}) { 77 | changedServices := make([]common.DubboServiceInstance, 0) 78 | 79 | callback := func() { 80 | c.mutex.Lock() 81 | defer c.mutex.Unlock() 82 | serviceEntries := common.ConvertServiceEntry(c.registryName, changedServices) 83 | for _, new := range serviceEntries { 84 | common.SyncServices2IstioUntilMaxRetries(new, c.registryName, c.ic) 85 | } 86 | } 87 | debouncer := debounce.New(debounceAfter, debounceMax, callback, stop) 88 | 89 | for { 90 | select { 91 | case services := <-c.eventChan: 92 | c.mutex.Lock() 93 | changedServices = append(changedServices, services...) 94 | c.mutex.Unlock() 95 | debouncer.Bounce() 96 | case <-stop: 97 | return 98 | } 99 | } 100 | } 101 | 102 | func (c *Controller) watchNamespace(stop <-chan struct{}) { 103 | ticker := time.NewTicker(10 * time.Second) 104 | for { 105 | select { 106 | case <-ticker.C: 107 | log.Infof("get all namespaces") 108 | nameSpaces, err := c.nacosClient.GetAllNamespaces() 109 | if err != nil { 110 | log.Errorf("failed to get all namespaces: %v", err) 111 | } 112 | for _, ns := range nameSpaces { 113 | if !c.watchedNamespace[ns.Namespace] { 114 | watcher, err := watcher.NewNamespaceWatcher(c.ncAddr, ns.Namespace, c.eventChan) 115 | if err != nil { 116 | log.Errorf("failed to watch namespace %s", ns.Namespace, err) 117 | } else { 118 | go watcher.Run(stop) 119 | c.watchedNamespace[ns.Namespace] = true 120 | log.Infof("start watching namespace %s", ns.Namespace) 121 | } 122 | } 123 | } 124 | case <-stop: 125 | return 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pkg/dubbo/nacos/model/conversion.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/common" 19 | 20 | nacosmodel "github.com/nacos-group/nacos-sdk-go/model" 21 | "istio.io/pkg/log" 22 | ) 23 | 24 | // ConvertDubboServices converts SubscribeServices to DubboServices 25 | func ConvertDubboServices(registryNS, registryGroup string, 26 | subscribeServices []nacosmodel.SubscribeService) ([]common.DubboServiceInstance, error) { 27 | instances := make([]common.DubboServiceInstance, 0) 28 | 29 | for _, subscribeService := range subscribeServices { 30 | dubboInterface := subscribeService.Metadata["interface"] 31 | if dubboInterface == "" { 32 | log.Errorf("invalid dubbo service instance: interface is missing: %v", subscribeService.Metadata) 33 | continue 34 | } 35 | instances = append(instances, common.DubboServiceInstance{ 36 | IP: subscribeService.Ip, 37 | Port: uint32(subscribeService.Port), 38 | Interface: dubboInterface, 39 | Namespace: registryNS, 40 | Group: registryGroup, 41 | Metadata: common.CopyMap(subscribeService.Metadata), 42 | }) 43 | } 44 | return instances, nil 45 | } 46 | -------------------------------------------------------------------------------- /pkg/dubbo/nacos/watcher/naemspace.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package watcher 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/common" 21 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/nacos/model" 22 | "github.com/nacos-group/nacos-sdk-go/clients/naming_client" 23 | nacosmodel "github.com/nacos-group/nacos-sdk-go/model" 24 | "github.com/nacos-group/nacos-sdk-go/vo" 25 | 26 | "istio.io/pkg/log" 27 | ) 28 | 29 | // NamespaceWatcher contains the runtime configuration for a Nacos Namespace Watcher 30 | type NamespaceWatcher struct { 31 | namespace string 32 | nacosClient naming_client.INamingClient 33 | subscribedServices map[string]bool 34 | notifyChan chan<- []common.DubboServiceInstance 35 | } 36 | 37 | // NewNamespaceWatcher creates a Namespace Watcher 38 | func NewNamespaceWatcher(nacosAddr, namespace string, notifyChan chan<- []common.DubboServiceInstance) ( 39 | *NamespaceWatcher, error) { 40 | nacosClient, error := common.NewNacosClisent(nacosAddr, namespace) 41 | if error != nil { 42 | return nil, error 43 | } 44 | return &NamespaceWatcher{ 45 | namespace: namespace, 46 | nacosClient: nacosClient, 47 | subscribedServices: make(map[string]bool), 48 | notifyChan: notifyChan, 49 | }, nil 50 | } 51 | 52 | // Run until a signal is received, this function blocks 53 | func (w *NamespaceWatcher) Run(stop <-chan struct{}) { 54 | ticker := time.NewTicker(10 * time.Second) 55 | for { 56 | select { 57 | case <-ticker.C: 58 | log.Infof("get catalog services of namespace %s ", w.namespace) 59 | catalogServiceList, err := w.nacosClient.GetCatalogServices(w.namespace) 60 | if err != nil { 61 | log.Errorf("failed to get catalog services of namespace %s : %v", w.namespace, err) 62 | continue 63 | } 64 | for _, catalogService := range catalogServiceList.ServiceList { 65 | if !w.subscribedServices[catalogService.Name+catalogService.GroupName] { 66 | err := w.nacosClient.Subscribe(&vo.SubscribeParam{ 67 | ServiceName: catalogService.Name, 68 | GroupName: catalogService.GroupName, 69 | Clusters: []string{}, 70 | SubscribeCallback: func(services []nacosmodel.SubscribeService, err error) { 71 | if err != nil { 72 | log.Errorf("failed to get notification: %v", err) 73 | } 74 | log.Infof("service %s changed: %v", catalogService.Name, len(services)) 75 | erviceEntries, err := model.ConvertDubboServices(w.namespace, 76 | catalogService.GroupName, 77 | services) 78 | if err != nil { 79 | log.Errorf("failed to convert dubbo service: %v", err) 80 | } 81 | w.notifyChan <- erviceEntries 82 | }, 83 | }) 84 | if err != nil { 85 | log.Errorf("failed to subscribe to service: %s group: %s: %v", catalogService.Name, 86 | catalogService.GroupName, 87 | err) 88 | } else { 89 | log.Infof("subscribe to service: %s group: %s", catalogService.Name, catalogService.GroupName) 90 | w.subscribedServices[catalogService.Name+catalogService.GroupName] = true 91 | } 92 | } 93 | } 94 | case <-stop: 95 | return 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /pkg/dubbo/zk/controller.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zk 16 | 17 | import ( 18 | "strings" 19 | "time" 20 | 21 | watcher "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/zk/watcher" 22 | "github.com/go-zookeeper/zk" 23 | istioclient "istio.io/client-go/pkg/clientset/versioned" 24 | "istio.io/pkg/log" 25 | ) 26 | 27 | // Controller creates a ZK controller 28 | type Controller struct { 29 | registryName string // registryName is the globally unique name of a dubbo registry 30 | zkAddr string 31 | isClient *istioclient.Clientset 32 | } 33 | 34 | // NewController creates a ZK controller 35 | func NewController(registryName string, zkAddr string, client *istioclient.Clientset) *Controller { 36 | return &Controller{ 37 | registryName: registryName, 38 | zkAddr: zkAddr, 39 | isClient: client, 40 | } 41 | } 42 | 43 | // Run until a signal is received, this function won't block 44 | func (c *Controller) Run(stop <-chan struct{}) { 45 | hosts := strings.Split(c.zkAddr, ",") 46 | conn := connectZKUntilSuccess(hosts) 47 | serviceWatcher := watcher.NewServiceWatcher(conn, c.isClient, c.registryName) 48 | go serviceWatcher.Run(stop) 49 | } 50 | 51 | func connectZKUntilSuccess(hosts []string) *zk.Conn { 52 | option := zk.WithEventCallback(callback) 53 | conn, _, err := zk.Connect(hosts, time.Second*10, option) 54 | //Keep trying to connect to ZK until succeed 55 | for err != nil { 56 | log.Errorf("failed to connect to ZooKeeper %s ,retrying : %v", hosts, err) 57 | time.Sleep(time.Second) 58 | conn, _, err = zk.Connect(hosts, time.Second*10, option) 59 | } 60 | return conn 61 | } 62 | 63 | func callback(event zk.Event) { 64 | log.Debug("--------ZK Event--------") 65 | log.Debug("path:", event.Path) 66 | log.Debug("type:", event.Type.String()) 67 | log.Debug("state:", event.State.String()) 68 | log.Debug("-------------------------") 69 | } 70 | -------------------------------------------------------------------------------- /pkg/dubbo/zk/model/conversion.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "regexp" 19 | "strconv" 20 | "strings" 21 | 22 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/common" 23 | 24 | "istio.io/client-go/pkg/apis/networking/v1alpha3" 25 | "istio.io/pkg/log" 26 | ) 27 | 28 | var providerRegexp *regexp.Regexp 29 | 30 | func init() { 31 | providerRegexp = regexp.MustCompile("dubbo%3A%2F%2F(.*)%3A([0-9]+)%2F(.*)%3F(.*)") 32 | } 33 | 34 | // ConvertServiceEntry converts dubbo provider znode to a service entry 35 | func ConvertServiceEntry(registryName string, dubboProviders []string) (map[string]*v1alpha3.ServiceEntry, error) { 36 | instances := make([]common.DubboServiceInstance, 0) 37 | 38 | for _, provider := range dubboProviders { 39 | dubboAttributes := parseProvider(provider) 40 | dubboInterface := dubboAttributes["interface"] 41 | instanceIP := dubboAttributes["ip"] 42 | dubboPort := dubboAttributes["port"] 43 | if dubboInterface == "" || instanceIP == "" || dubboPort == "" { 44 | log.Errorf("failed to convert dubbo provider to workloadEntry, parameters service, "+ 45 | "interface, ip or port is missing: %s", provider) 46 | continue 47 | } 48 | 49 | port, err := strconv.Atoi(dubboPort) 50 | if err != nil { 51 | log.Errorf("failed to convert dubbo port to int: %s, ignore provider %s", dubboPort, provider) 52 | continue 53 | } 54 | 55 | instances = append(instances, common.DubboServiceInstance{ 56 | IP: instanceIP, 57 | Port: uint32(port), 58 | Interface: dubboInterface, 59 | Namespace: "", 60 | Group: "", 61 | Metadata: dubboAttributes, 62 | }) 63 | } 64 | return common.ConvertServiceEntry(registryName, instances), nil 65 | } 66 | 67 | func parseProvider(provider string) map[string]string { 68 | dubboAttributes := make(map[string]string) 69 | result := providerRegexp.FindStringSubmatch(provider) 70 | if len(result) == 5 { 71 | dubboAttributes["ip"] = result[1] 72 | dubboAttributes["port"] = result[2] 73 | dubboAttributes["service"] = result[3] 74 | parameters := strings.Split(result[4], "%26") 75 | for _, parameter := range parameters { 76 | keyValuePair := strings.Split(parameter, "%3D") 77 | if len(keyValuePair) == 2 { 78 | dubboAttributes[keyValuePair[0]] = strings.ReplaceAll(keyValuePair[1], "%2C", "-") 79 | } 80 | } 81 | } 82 | return dubboAttributes 83 | } 84 | -------------------------------------------------------------------------------- /pkg/dubbo/zk/model/conversion_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package model 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func Test_parseProvider(t *testing.T) { 22 | dubboProvider := "dubbo%3A%2F%2F172.18.0.9%3A20880%2Forg.apache.dubbo.samples.basic.api.DemoService%3F" + 23 | "anyhost%3Dtrue%26application%3Ddemo-provider%26default%3Dtrue%26deprecated%3Dfalse%26dubbo%3D2.0.2" + 24 | "%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.samples.basic.api.DemoService%26" + 25 | "methods%3DtestVoid%2CsayHello%26pid%3D6%26release%3D1.0-SNAPSHOT%26revision%3D1.0-SNAPSHOT%26side%3D" + 26 | "provider%26timestamp%3D1618918522655" 27 | attributes := parseProvider(dubboProvider) 28 | if attributes["ip"] != "172.18.0.9" { 29 | t.Errorf("parseProvider ip => %v, want %v", attributes["ip"], "172.18.0.9") 30 | } 31 | if attributes["port"] != "20880" { 32 | t.Errorf("parseProvider port => %v, want %v", attributes["port"], "20880") 33 | } 34 | if attributes["interface"] != "org.apache.dubbo.samples.basic.api.DemoService" { 35 | t.Errorf("parseProvider port => %v, want %v", attributes["interface"], "org.apache.dubbo.samples.basic.api.DemoService") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/dubbo/zk/watcher/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zk 16 | 17 | import ( 18 | "sync" 19 | "time" 20 | 21 | "github.com/zhaohuabing/debounce" 22 | 23 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/common" 24 | "github.com/aeraki-mesh/dubbo2istio/pkg/dubbo/zk/model" 25 | 26 | "github.com/go-zookeeper/zk" 27 | istioclient "istio.io/client-go/pkg/clientset/versioned" 28 | "istio.io/pkg/log" 29 | ) 30 | 31 | const ( 32 | // debounceAfter is the delay added to events to wait after a registry event for debouncing. 33 | // This will delay the push by at least this interval, plus the time getting subsequent events. 34 | // If no change is detected the push will happen, otherwise we'll keep delaying until things settle. 35 | debounceAfter = 3 * time.Second 36 | 37 | // debounceMax is the maximum time to wait for events while debouncing. 38 | // Defaults to 10 seconds. If events keep showing up with no break for this time, we'll trigger a push. 39 | debounceMax = 10 * time.Second 40 | ) 41 | 42 | // ProviderWatcher watches changes on dubbo service providers and synchronize the changed dubbo providers to the Istio 43 | // control plane via service entries 44 | type ProviderWatcher struct { 45 | mutex sync.Mutex 46 | service string 47 | path string 48 | conn *zk.Conn 49 | ic *istioclient.Clientset 50 | serviceEntryNS map[string]string // key service entry name, value namespace 51 | registryName string // registryName is the globally unique name of a dubbo registry 52 | } 53 | 54 | // NewProviderWatcher creates a ProviderWatcher 55 | func NewProviderWatcher(ic *istioclient.Clientset, conn *zk.Conn, service string, registryName string) *ProviderWatcher { 56 | path := "/dubbo/" + service + "/providers" 57 | return &ProviderWatcher{ 58 | service: service, 59 | path: path, 60 | conn: conn, 61 | ic: ic, 62 | serviceEntryNS: make(map[string]string), 63 | registryName: registryName, 64 | } 65 | } 66 | 67 | // Run starts the ProviderWatcher until it receives a message over the stop channel 68 | // This method blocks the caller 69 | func (w *ProviderWatcher) Run(stop <-chan struct{}) { 70 | providers, eventChan := watchUntilSuccess(w.path, w.conn) 71 | w.syncServices2Istio(w.service, providers) 72 | 73 | callback := func() { 74 | w.mutex.Lock() 75 | defer w.mutex.Unlock() 76 | w.syncServices2Istio(w.service, providers) 77 | } 78 | debouncer := debounce.New(debounceAfter, debounceMax, callback, stop) 79 | for { 80 | select { 81 | case <-eventChan: 82 | w.mutex.Lock() 83 | providers, eventChan = watchUntilSuccess(w.path, w.conn) 84 | w.mutex.Unlock() 85 | debouncer.Bounce() 86 | case <-stop: 87 | return 88 | } 89 | } 90 | } 91 | 92 | func (w *ProviderWatcher) syncServices2Istio(service string, providers []string) { 93 | if len(providers) == 0 { 94 | log.Warnf("Service %s has no providers, ignore synchronize job", service) 95 | return 96 | } 97 | serviceEntries, err := model.ConvertServiceEntry(w.registryName, providers) 98 | if err != nil { 99 | log.Errorf("Failed to synchronize dubbo services to Istio: %v", err) 100 | } 101 | 102 | for _, new := range serviceEntries { 103 | common.SyncServices2IstioUntilMaxRetries(new, w.registryName, w.ic) 104 | } 105 | } 106 | 107 | func watchUntilSuccess(path string, conn *zk.Conn) ([]string, <-chan zk.Event) { 108 | providers, _, eventChan, err := conn.ChildrenW(path) 109 | //Retry until succeed 110 | for err != nil { 111 | log.Errorf("failed to watch zookeeper path %s, %v", path, err) 112 | time.Sleep(1 * time.Second) 113 | providers, _, eventChan, err = conn.ChildrenW(path) 114 | } 115 | return providers, eventChan 116 | } 117 | -------------------------------------------------------------------------------- /pkg/dubbo/zk/watcher/service.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zk 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/go-zookeeper/zk" 21 | istioclient "istio.io/client-go/pkg/clientset/versioned" 22 | "istio.io/pkg/log" 23 | ) 24 | 25 | const ( 26 | dubboRegistryPath = "/dubbo" 27 | ) 28 | 29 | // ServiceWatcher watches for newly created dubbo services and creates a providerWatcher for each service 30 | type ServiceWatcher struct { 31 | path string 32 | conn *zk.Conn 33 | ic *istioclient.Clientset 34 | providerWatchers map[string]*ProviderWatcher 35 | registryName string // registryName is the globally unique name of a dubbo registry 36 | } 37 | 38 | // NewServiceWatcher creates a ServiceWatcher 39 | func NewServiceWatcher(conn *zk.Conn, clientset *istioclient.Clientset, registryName string) *ServiceWatcher { 40 | return &ServiceWatcher{ 41 | ic: clientset, 42 | path: dubboRegistryPath, 43 | conn: conn, 44 | providerWatchers: make(map[string]*ProviderWatcher), 45 | registryName: registryName, 46 | } 47 | } 48 | 49 | // Run starts the ServiceWatcher until it receives a message over the stop channel. 50 | // This method blocks the caller 51 | func (w *ServiceWatcher) Run(stop <-chan struct{}) { 52 | w.waitFroDubboRootPath() 53 | eventChan := w.watchProviders(stop) 54 | for { 55 | select { 56 | case event := <-eventChan: 57 | log.Infof("received event : %s, %v", event.Type.String(), event) 58 | eventChan = w.watchProviders(stop) 59 | case <-stop: 60 | return 61 | } 62 | } 63 | } 64 | 65 | func (w *ServiceWatcher) waitFroDubboRootPath() { 66 | exists := false 67 | for !exists { 68 | var err error 69 | exists, _, err = w.conn.Exists(w.path) 70 | if err != nil { 71 | log.Errorf("failed to check path existence : %v", err) 72 | } 73 | if !exists { 74 | log.Warnf("zookeeper path " + dubboRegistryPath + " doesn't exist, wait until it's created") 75 | } 76 | time.Sleep(time.Second * 2) 77 | } 78 | } 79 | 80 | func (w *ServiceWatcher) watchProviders(stop <-chan struct{}) <-chan zk.Event { 81 | children, newChan := watchUntilSuccess(w.path, w.conn) 82 | for _, node := range children { 83 | //skip config and metadata node 84 | if node == "config" || node == "metadata" { 85 | continue 86 | } 87 | if _, exists := w.providerWatchers[node]; !exists { 88 | providerWatcher := NewProviderWatcher(w.ic, w.conn, node, w.registryName) 89 | w.providerWatchers[node] = providerWatcher 90 | log.Infof("start to watch service %s on zookeeper", node) 91 | go providerWatcher.Run(stop) 92 | } 93 | } 94 | return newChan 95 | } 96 | -------------------------------------------------------------------------------- /test/e2e/common.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package e2e 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | "time" 21 | 22 | "github.com/aeraki-mesh/dubbo2istio/test/e2e/util" 23 | ) 24 | 25 | // TestCreateServiceEntry tests creating service entry 26 | func TestCreateServiceEntry(t *testing.T) { 27 | err := util.WaitForDeploymentsReady("meta-dubbo", 10*time.Minute, "") 28 | if err != nil { 29 | t.Errorf("failed to wait for deployment: %v", err) 30 | } 31 | //wait 120 seconds for service entries to be created 32 | time.Sleep(120 * time.Second) 33 | 34 | serviceEntries, err := util.KubeGetYaml("meta-dubbo", "serviceentry", "", "") 35 | if err != nil { 36 | t.Errorf("failed to get serviceentry %v", err) 37 | } 38 | 39 | expectedServices := []string{ 40 | "org.apache.dubbo.samples.basic.api.complexservice-product-2-0-0", 41 | "org.apache.dubbo.samples.basic.api.complexservice-test-1-0-0", 42 | "org.apache.dubbo.samples.basic.api.demoservice", 43 | "org.apache.dubbo.samples.basic.api.testservice"} 44 | for _, service := range expectedServices { 45 | if !strings.Contains(serviceEntries, service) { 46 | t.Errorf("can't find expected serviceentry: %s", service) 47 | } 48 | } 49 | 50 | serviceEntry, err := util.Shell("kubectl get serviceentry aeraki-org-apache-dubbo-samples-basic-api-demoservice" + 51 | " -n meta-dubbo -o=jsonpath='{range .items[*]}{.spec.endpoints}'") 52 | if err != nil { 53 | t.Errorf("failed to get serviceentry %v", err) 54 | } 55 | count := strings.Count(serviceEntry, "address") 56 | if count != 2 { 57 | t.Errorf("endpoint number is not correct, expect: %v, get %v", 2, count) 58 | } 59 | } 60 | 61 | // TestDeleteServiceEntry tests deleting service entry 62 | func TestDeleteServiceEntry(t *testing.T) { 63 | _, err := util.Shell("kubectl delete deploy dubbo-sample-provider-v1 -n meta-dubbo") 64 | if err != nil { 65 | t.Errorf("failed to delete deploy %v", err) 66 | } 67 | 68 | //wait 60 seconds for the endpoint to be deleted 69 | time.Sleep(120 * time.Second) 70 | 71 | serviceEntry, err := util.Shell("kubectl get serviceentry aeraki-org-apache-dubbo-samples-basic-api-demoservice -n meta-dubbo -o=jsonpath='{range .items[*]}{.spec.endpoints}'") 72 | if err != nil { 73 | t.Errorf("failed to get serviceentry %v", err) 74 | } 75 | count := strings.Count(serviceEntry, "address") 76 | if count != 1 { 77 | t.Errorf("endpoint number is not correct, expect: %v, get %v", 1, count) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/e2e/common/aeraki-crd.yaml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs. 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | "helm.sh/resource-policy": keep 7 | labels: 8 | app: aeraki 9 | chart: aeraki 10 | heritage: Tiller 11 | release: aeraki 12 | name: dubboauthorizationpolicies.dubbo.aeraki.io 13 | spec: 14 | group: dubbo.aeraki.io 15 | names: 16 | categories: 17 | - aeraki-io 18 | - dubbo-aeraki-io 19 | kind: DubboAuthorizationPolicy 20 | listKind: DubboAuthorizationPolicyList 21 | plural: dubboauthorizationpolicies 22 | shortNames: 23 | - dap 24 | singular: dubboauthorizationpolicy 25 | scope: Namespaced 26 | versions: 27 | - name: v1alpha1 28 | schema: 29 | openAPIV3Schema: 30 | properties: 31 | spec: 32 | description: DubboAuthorizationPolicy enables access control on Dubbo 33 | services. 34 | properties: 35 | action: 36 | description: Optional. 37 | enum: 38 | - ALLOW 39 | - DENY 40 | type: string 41 | rules: 42 | description: Optional. 43 | items: 44 | properties: 45 | from: 46 | description: Optional. 47 | items: 48 | properties: 49 | source: 50 | description: Source specifies the source of a request. 51 | properties: 52 | namespaces: 53 | description: Optional. 54 | items: 55 | format: string 56 | type: string 57 | type: array 58 | notNamespaces: 59 | description: Optional. 60 | items: 61 | format: string 62 | type: string 63 | type: array 64 | notPrincipals: 65 | description: Optional. 66 | items: 67 | format: string 68 | type: string 69 | type: array 70 | principals: 71 | description: Optional. 72 | items: 73 | format: string 74 | type: string 75 | type: array 76 | type: object 77 | type: object 78 | type: array 79 | to: 80 | description: Optional. 81 | items: 82 | properties: 83 | operation: 84 | description: Operation specifies the operation of a request. 85 | properties: 86 | interfaces: 87 | description: Optional. 88 | items: 89 | format: string 90 | type: string 91 | type: array 92 | methods: 93 | description: Optional. 94 | items: 95 | format: string 96 | type: string 97 | type: array 98 | notInterfaces: 99 | description: Optional. 100 | items: 101 | format: string 102 | type: string 103 | type: array 104 | notMethods: 105 | description: Optional. 106 | items: 107 | format: string 108 | type: string 109 | type: array 110 | type: object 111 | type: object 112 | type: array 113 | type: object 114 | type: array 115 | type: object 116 | status: 117 | type: object 118 | x-kubernetes-preserve-unknown-fields: true 119 | type: object 120 | served: true 121 | storage: true 122 | subresources: 123 | status: {} 124 | 125 | --- 126 | apiVersion: apiextensions.k8s.io/v1 127 | kind: CustomResourceDefinition 128 | metadata: 129 | annotations: 130 | "helm.sh/resource-policy": keep 131 | labels: 132 | app: aeraki 133 | chart: aeraki 134 | heritage: Tiller 135 | release: aeraki 136 | name: applicationprotocols.metaprotocol.aeraki.io 137 | spec: 138 | group: metaprotocol.aeraki.io 139 | names: 140 | categories: 141 | - aeraki-io 142 | - metaprotocol-aeraki-io 143 | kind: ApplicationProtocol 144 | listKind: ApplicationProtocolList 145 | plural: applicationprotocols 146 | singular: applicationprotocol 147 | scope: Namespaced 148 | versions: 149 | - name: v1alpha1 150 | schema: 151 | openAPIV3Schema: 152 | properties: 153 | spec: 154 | description: ApplicationProtocol defines an application protocol built 155 | on top of MetaProtocol. 156 | properties: 157 | codec: 158 | format: string 159 | type: string 160 | protocol: 161 | format: string 162 | type: string 163 | type: object 164 | status: 165 | type: object 166 | x-kubernetes-preserve-unknown-fields: true 167 | type: object 168 | served: true 169 | storage: true 170 | subresources: 171 | status: {} 172 | 173 | --- 174 | apiVersion: apiextensions.k8s.io/v1 175 | kind: CustomResourceDefinition 176 | metadata: 177 | annotations: 178 | "helm.sh/resource-policy": keep 179 | labels: 180 | app: aeraki 181 | chart: aeraki 182 | heritage: Tiller 183 | release: aeraki 184 | name: metarouters.metaprotocol.aeraki.io 185 | spec: 186 | group: metaprotocol.aeraki.io 187 | names: 188 | categories: 189 | - aeraki-io 190 | - metaprotocol-aeraki-io 191 | kind: MetaRouter 192 | listKind: MetaRouterList 193 | plural: metarouters 194 | singular: metarouter 195 | scope: Namespaced 196 | versions: 197 | - name: v1alpha1 198 | schema: 199 | openAPIV3Schema: 200 | properties: 201 | spec: 202 | description: MetaRouter defines route policies for MetaProtocol proxy. 203 | properties: 204 | globalRateLimit: 205 | properties: 206 | denyOnFail: 207 | type: boolean 208 | descriptors: 209 | items: 210 | properties: 211 | descriptorKey: 212 | format: string 213 | type: string 214 | property: 215 | format: string 216 | type: string 217 | type: object 218 | type: array 219 | domain: 220 | description: The rate limit domain to use when calling the rate 221 | limit service. 222 | format: string 223 | type: string 224 | match: 225 | description: Match conditions to be satisfied for the rate limit 226 | rule to be activated. 227 | properties: 228 | attributes: 229 | additionalProperties: 230 | oneOf: 231 | - not: 232 | anyOf: 233 | - required: 234 | - exact 235 | - required: 236 | - prefix 237 | - required: 238 | - regex 239 | - required: 240 | - exact 241 | - required: 242 | - prefix 243 | - required: 244 | - regex 245 | properties: 246 | exact: 247 | format: string 248 | type: string 249 | prefix: 250 | format: string 251 | type: string 252 | regex: 253 | description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). 254 | format: string 255 | type: string 256 | type: object 257 | description: If the value is empty and only the name of attribute 258 | is specified, presence of the attribute is checked. 259 | type: object 260 | type: object 261 | rateLimitService: 262 | description: The cluster name of the external rate limit service 263 | provider. 264 | format: string 265 | type: string 266 | requestTimeout: 267 | description: The timeout in milliseconds for the rate limit service 268 | RPC. 269 | type: string 270 | type: object 271 | hosts: 272 | items: 273 | format: string 274 | type: string 275 | type: array 276 | localRateLimit: 277 | properties: 278 | conditions: 279 | description: The more specific rate limit conditions, the first 280 | match will be used. 281 | items: 282 | properties: 283 | match: 284 | description: Match conditions to be satisfied for the rate 285 | limit rule to be activated. 286 | properties: 287 | attributes: 288 | additionalProperties: 289 | oneOf: 290 | - not: 291 | anyOf: 292 | - required: 293 | - exact 294 | - required: 295 | - prefix 296 | - required: 297 | - regex 298 | - required: 299 | - exact 300 | - required: 301 | - prefix 302 | - required: 303 | - regex 304 | properties: 305 | exact: 306 | format: string 307 | type: string 308 | prefix: 309 | format: string 310 | type: string 311 | regex: 312 | description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). 313 | format: string 314 | type: string 315 | type: object 316 | description: If the value is empty and only the name 317 | of attribute is specified, presence of the attribute 318 | is checked. 319 | type: object 320 | type: object 321 | tokenBucket: 322 | properties: 323 | fillInterval: 324 | description: The fill interval that tokens are added 325 | to the bucket. 326 | type: string 327 | maxTokens: 328 | description: The maximum tokens that the bucket can 329 | hold. 330 | type: integer 331 | tokensPerFill: 332 | description: The number of tokens added to the bucket 333 | during each fill interval. 334 | nullable: true 335 | type: integer 336 | type: object 337 | type: object 338 | type: array 339 | tokenBucket: 340 | properties: 341 | fillInterval: 342 | description: The fill interval that tokens are added to the 343 | bucket. 344 | type: string 345 | maxTokens: 346 | description: The maximum tokens that the bucket can hold. 347 | type: integer 348 | tokensPerFill: 349 | description: The number of tokens added to the bucket during 350 | each fill interval. 351 | nullable: true 352 | type: integer 353 | type: object 354 | type: object 355 | routes: 356 | items: 357 | properties: 358 | match: 359 | description: Match conditions to be satisfied for the rule to 360 | be activated. 361 | properties: 362 | attributes: 363 | additionalProperties: 364 | oneOf: 365 | - not: 366 | anyOf: 367 | - required: 368 | - exact 369 | - required: 370 | - prefix 371 | - required: 372 | - regex 373 | - required: 374 | - exact 375 | - required: 376 | - prefix 377 | - required: 378 | - regex 379 | properties: 380 | exact: 381 | format: string 382 | type: string 383 | prefix: 384 | format: string 385 | type: string 386 | regex: 387 | description: RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax). 388 | format: string 389 | type: string 390 | type: object 391 | description: If the value is empty and only the name of 392 | attribute is specified, presence of the attribute is checked. 393 | type: object 394 | type: object 395 | name: 396 | description: The name assigned to the route for debugging purposes. 397 | format: string 398 | type: string 399 | requestMutation: 400 | description: Specifies a list of key-value pairs that should 401 | be mutated for each request. 402 | items: 403 | properties: 404 | key: 405 | description: Key name. 406 | format: string 407 | type: string 408 | value: 409 | description: alue. 410 | format: string 411 | type: string 412 | type: object 413 | type: array 414 | responseMutation: 415 | description: Specifies a list of key-value pairs that should 416 | be mutated for each response. 417 | items: 418 | properties: 419 | key: 420 | description: Key name. 421 | format: string 422 | type: string 423 | value: 424 | description: alue. 425 | format: string 426 | type: string 427 | type: object 428 | type: array 429 | route: 430 | description: A Route rule can forward (default) traffic. 431 | items: 432 | properties: 433 | destination: 434 | properties: 435 | host: 436 | description: The name of a service from the service 437 | registry. 438 | format: string 439 | type: string 440 | port: 441 | description: Specifies the port on the host that is 442 | being addressed. 443 | properties: 444 | number: 445 | type: integer 446 | type: object 447 | subset: 448 | description: The name of a subset within the service. 449 | format: string 450 | type: string 451 | type: object 452 | weight: 453 | type: integer 454 | type: object 455 | type: array 456 | type: object 457 | type: array 458 | type: object 459 | status: 460 | type: object 461 | x-kubernetes-preserve-unknown-fields: true 462 | type: object 463 | served: true 464 | storage: true 465 | subresources: 466 | status: {} 467 | 468 | --- 469 | apiVersion: apiextensions.k8s.io/v1 470 | kind: CustomResourceDefinition 471 | metadata: 472 | name: redisdestinations.redis.aeraki.io 473 | spec: 474 | group: redis.aeraki.io 475 | names: 476 | categories: 477 | - redis-aeraki-io 478 | kind: RedisDestination 479 | listKind: RedisDestinationList 480 | plural: redisdestinations 481 | shortNames: 482 | - rd 483 | singular: redisdestination 484 | scope: Namespaced 485 | versions: 486 | - additionalPrinterColumns: 487 | - description: The name of a service from the service registry 488 | jsonPath: .spec.host 489 | name: Host 490 | type: string 491 | - description: 'CreationTimestamp is a timestamp representing the server time 492 | when this object was created. It is not guaranteed to be set in happens-before 493 | order across separate operations. Clients may not set this value. It is represented 494 | in RFC3339 form and is in UTC. Populated by the system. Read-only. Null for 495 | lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata' 496 | jsonPath: .metadata.creationTimestamp 497 | name: Age 498 | type: date 499 | name: v1alpha1 500 | schema: 501 | openAPIV3Schema: 502 | properties: 503 | spec: 504 | properties: 505 | host: 506 | format: string 507 | type: string 508 | trafficPolicy: 509 | properties: 510 | connectionPool: 511 | properties: 512 | redis: 513 | properties: 514 | auth: 515 | oneOf: 516 | - not: 517 | anyOf: 518 | - required: 519 | - secret 520 | - required: 521 | - plain 522 | - required: 523 | - secret 524 | - required: 525 | - plain 526 | properties: 527 | plain: 528 | description: redis password. 529 | properties: 530 | password: 531 | format: string 532 | type: string 533 | username: 534 | format: string 535 | type: string 536 | type: object 537 | secret: 538 | description: Secret use the k8s secret in current 539 | namespace. 540 | properties: 541 | name: 542 | format: string 543 | type: string 544 | passwordField: 545 | format: string 546 | type: string 547 | usernameField: 548 | format: string 549 | type: string 550 | type: object 551 | type: object 552 | discoveryEndpoints: 553 | items: 554 | format: string 555 | type: string 556 | type: array 557 | mode: 558 | enum: 559 | - PROXY 560 | - CLUSTER 561 | type: string 562 | type: object 563 | tcp: 564 | properties: 565 | connectTimeout: 566 | description: TCP connection timeout. 567 | type: string 568 | maxConnections: 569 | description: Maximum number of HTTP1 /TCP connections 570 | to a destination host. 571 | format: int32 572 | type: integer 573 | tcpKeepalive: 574 | description: If set then set SO_KEEPALIVE on the socket 575 | to enable TCP Keepalives. 576 | properties: 577 | interval: 578 | description: The time duration between keep-alive 579 | probes. 580 | type: string 581 | probes: 582 | type: integer 583 | time: 584 | type: string 585 | type: object 586 | type: object 587 | type: object 588 | type: object 589 | type: object 590 | status: 591 | type: object 592 | x-kubernetes-preserve-unknown-fields: true 593 | type: object 594 | served: true 595 | storage: true 596 | subresources: 597 | status: {} 598 | 599 | --- 600 | apiVersion: apiextensions.k8s.io/v1 601 | kind: CustomResourceDefinition 602 | metadata: 603 | name: redisservices.redis.aeraki.io 604 | spec: 605 | group: redis.aeraki.io 606 | names: 607 | categories: 608 | - redis-aeraki-io 609 | kind: RedisService 610 | listKind: RedisServiceList 611 | plural: redisservices 612 | shortNames: 613 | - rsvc 614 | singular: redisservice 615 | scope: Namespaced 616 | versions: 617 | - additionalPrinterColumns: 618 | - description: The destination hosts to which traffic is being sent 619 | jsonPath: .spec.hosts 620 | name: Hosts 621 | type: string 622 | - description: 'CreationTimestamp is a timestamp representing the server time 623 | when this object was created. It is not guaranteed to be set in happens-before 624 | order across separate operations. Clients may not set this value. It is represented 625 | in RFC3339 form and is in UTC. Populated by the system. Read-only. Null for 626 | lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata' 627 | jsonPath: .metadata.creationTimestamp 628 | name: Age 629 | type: date 630 | name: v1alpha1 631 | schema: 632 | openAPIV3Schema: 633 | properties: 634 | spec: 635 | description: RedisService provide a way to config redis service in service 636 | mesh. 637 | properties: 638 | faults: 639 | description: List of faults to inject. 640 | items: 641 | properties: 642 | commands: 643 | description: Commands fault is restricted to, if any. 644 | items: 645 | format: string 646 | type: string 647 | type: array 648 | delay: 649 | description: Delay for all faults. 650 | type: string 651 | percentage: 652 | description: Percentage of requests fault applies to. 653 | properties: 654 | value: 655 | format: double 656 | type: number 657 | type: object 658 | type: 659 | description: Fault type. 660 | enum: 661 | - DELAY 662 | - ERROR 663 | type: string 664 | type: object 665 | type: array 666 | host: 667 | items: 668 | format: string 669 | type: string 670 | type: array 671 | redis: 672 | items: 673 | properties: 674 | match: 675 | oneOf: 676 | - not: 677 | anyOf: 678 | - required: 679 | - key 680 | - required: 681 | - key 682 | properties: 683 | key: 684 | properties: 685 | prefix: 686 | description: String prefix that must match the beginning 687 | of the keys. 688 | format: string 689 | type: string 690 | removePrefix: 691 | description: Indicates if the prefix needs to be removed 692 | from the key when forwarded. 693 | type: boolean 694 | type: object 695 | type: object 696 | mirror: 697 | items: 698 | properties: 699 | excludeReadCommands: 700 | type: boolean 701 | percentage: 702 | properties: 703 | value: 704 | format: double 705 | type: number 706 | type: object 707 | route: 708 | properties: 709 | host: 710 | format: string 711 | type: string 712 | port: 713 | type: integer 714 | type: object 715 | type: object 716 | type: array 717 | route: 718 | properties: 719 | host: 720 | format: string 721 | type: string 722 | port: 723 | type: integer 724 | type: object 725 | type: object 726 | type: array 727 | settings: 728 | properties: 729 | auth: 730 | description: Downstream auth. 731 | oneOf: 732 | - not: 733 | anyOf: 734 | - required: 735 | - secret 736 | - required: 737 | - plain 738 | - required: 739 | - secret 740 | - required: 741 | - plain 742 | properties: 743 | plain: 744 | description: redis password. 745 | properties: 746 | password: 747 | format: string 748 | type: string 749 | username: 750 | format: string 751 | type: string 752 | type: object 753 | secret: 754 | description: Secret use the k8s secret in current namespace. 755 | properties: 756 | name: 757 | format: string 758 | type: string 759 | passwordField: 760 | format: string 761 | type: string 762 | usernameField: 763 | format: string 764 | type: string 765 | type: object 766 | type: object 767 | bufferFlushTimeout: 768 | type: string 769 | caseInsensitive: 770 | description: Indicates that prefix matching should be case insensitive. 771 | type: boolean 772 | enableCommandStats: 773 | type: boolean 774 | enableHashtagging: 775 | type: boolean 776 | enableRedirection: 777 | type: boolean 778 | maxBufferSizeBeforeFlush: 779 | type: integer 780 | maxUpstreamUnknownConnections: 781 | nullable: true 782 | type: integer 783 | opTimeout: 784 | description: Per-operation timeout in milliseconds. 785 | type: string 786 | readPolicy: 787 | description: Read policy. 788 | enum: 789 | - MASTER 790 | - PREFER_MASTER 791 | - REPLICA 792 | - PREFER_REPLICA 793 | - ANY 794 | type: string 795 | type: object 796 | type: object 797 | status: 798 | type: object 799 | x-kubernetes-preserve-unknown-fields: true 800 | type: object 801 | served: true 802 | storage: true 803 | subresources: 804 | status: {} 805 | 806 | --- 807 | -------------------------------------------------------------------------------- /test/e2e/common/aeraki.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Aeraki Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | --- 16 | apiVersion: apps/v1 17 | kind: Deployment 18 | metadata: 19 | name: aeraki 20 | namespace: istio-system 21 | labels: 22 | app: aeraki 23 | spec: 24 | selector: 25 | matchLabels: 26 | app: aeraki 27 | replicas: 1 28 | template: 29 | metadata: 30 | labels: 31 | app: aeraki 32 | annotations: 33 | sidecar.istio.io/inject: "false" 34 | spec: 35 | serviceAccountName: aeraki 36 | containers: 37 | - name: aeraki 38 | image: aeraki/aeraki:${AERAKI_TAG} 39 | env: 40 | - name: AERAKI_IS_MASTER 41 | value: "${AERAKI_IS_MASTER}" 42 | - name: AERAKI_ISTIOD_ADDR 43 | value: ${AERAKI_ISTIOD_ADDR} 44 | - name: AERAKI_CLUSTER_ID 45 | value: ${AERAKI_CLUSTER_ID} 46 | # In case of TCM, Istio config store can be a different k8s API server from the one Aeraki is running with 47 | - name: AERAKI_ISTIO_CONFIG_STORE_SECRET 48 | value: ${AERAKI_ISTIO_CONFIG_STORE_SECRET} 49 | - name: AERAKI_XDS_LISTEN_ADDR 50 | value: ":15010" 51 | - name: AERAKI_LOG_LEVEL 52 | value: "all:debug" 53 | - name: AERAKI_NAMESPACE 54 | valueFrom: 55 | fieldRef: 56 | fieldPath: metadata.namespace 57 | - name: AERAKI_SERVER_ID 58 | valueFrom: 59 | fieldRef: 60 | fieldPath: metadata.name 61 | volumeMounts: 62 | - name: istiod-ca-cert 63 | mountPath: /var/run/secrets/istio 64 | readOnly: true 65 | volumes: 66 | - name: istiod-ca-cert 67 | configMap: 68 | name: istio-ca-root-cert 69 | defaultMode: 420 70 | --- 71 | apiVersion: v1 72 | kind: Service 73 | metadata: 74 | labels: 75 | app: aeraki-xds 76 | name: aeraki-xds 77 | namespace: istio-system 78 | spec: 79 | ports: 80 | - name: grpc-xds 81 | port: 15010 82 | protocol: TCP 83 | targetPort: 15010 84 | selector: 85 | app: aeraki 86 | --- 87 | apiVersion: v1 88 | kind: ServiceAccount 89 | metadata: 90 | name: aeraki 91 | namespace: istio-system 92 | --- 93 | apiVersion: rbac.authorization.k8s.io/v1 94 | kind: Role 95 | metadata: 96 | labels: 97 | app: aeraki 98 | name: aeraki 99 | namespace: istio-system 100 | rules: 101 | - apiGroups: 102 | - "" 103 | resources: 104 | - configmaps 105 | - events 106 | verbs: 107 | - '*' 108 | - verbs: 109 | - '*' 110 | apiGroups: 111 | - coordination.k8s.io 112 | resources: 113 | - '*' 114 | --- 115 | apiVersion: rbac.authorization.k8s.io/v1 116 | kind: RoleBinding 117 | metadata: 118 | labels: 119 | app: aeraki 120 | name: aeraki 121 | namespace: istio-system 122 | roleRef: 123 | apiGroup: rbac.authorization.k8s.io 124 | kind: Role 125 | name: aeraki 126 | subjects: 127 | - kind: ServiceAccount 128 | name: aeraki 129 | --- 130 | apiVersion: rbac.authorization.k8s.io/v1 131 | kind: ClusterRole 132 | metadata: 133 | labels: 134 | app: aeraki 135 | name: aeraki 136 | rules: 137 | - apiGroups: 138 | - "" 139 | resources: 140 | - secrets 141 | verbs: 142 | - get 143 | - apiGroups: 144 | - networking.istio.io 145 | resources: 146 | - '*' 147 | verbs: 148 | - get 149 | - watch 150 | - list 151 | - apiGroups: 152 | - redis.aeraki.io 153 | - dubbo.aeraki.io 154 | - metaprotocol.aeraki.io 155 | resources: 156 | - '*' 157 | verbs: 158 | - get 159 | - watch 160 | - list 161 | - update 162 | - patch 163 | - create 164 | - delete 165 | - apiGroups: 166 | - networking.istio.io 167 | resources: 168 | - virtualservices 169 | - destinationrules 170 | - envoyfilters 171 | - serviceentries 172 | verbs: 173 | - get 174 | - watch 175 | - list 176 | - update 177 | - patch 178 | - create 179 | - delete 180 | --- 181 | apiVersion: rbac.authorization.k8s.io/v1 182 | kind: ClusterRoleBinding 183 | metadata: 184 | labels: 185 | app: aeraki 186 | name: aeraki 187 | roleRef: 188 | apiGroup: rbac.authorization.k8s.io 189 | kind: ClusterRole 190 | name: aeraki 191 | subjects: 192 | - kind: ServiceAccount 193 | name: aeraki 194 | namespace: istio-system 195 | --- 196 | apiVersion: metaprotocol.aeraki.io/v1alpha1 197 | kind: ApplicationProtocol 198 | metadata: 199 | name: dubbo 200 | namespace: istio-system 201 | spec: 202 | protocol: dubbo 203 | codec: aeraki.meta_protocol.codec.dubbo 204 | --- 205 | apiVersion: metaprotocol.aeraki.io/v1alpha1 206 | kind: ApplicationProtocol 207 | metadata: 208 | name: thrift 209 | namespace: istio-system 210 | spec: 211 | protocol: thrift 212 | codec: aeraki.meta_protocol.codec.thrift 213 | -------------------------------------------------------------------------------- /test/e2e/common/dubbo2istio-etcd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dubbo2istio 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | labels: 11 | app: dubbo2istio 12 | name: dubbo2istio 13 | rules: 14 | - apiGroups: 15 | - networking.istio.io 16 | resources: 17 | - serviceentries 18 | verbs: 19 | - get 20 | - watch 21 | - list 22 | - update 23 | - patch 24 | - create 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | labels: 31 | app: dubbo2istio 32 | name: dubbo2istio 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: dubbo2istio 37 | subjects: 38 | - kind: ServiceAccount 39 | name: dubbo2istio 40 | namespace: meta-dubbo 41 | --- 42 | apiVersion: apps/v1 43 | kind: Deployment 44 | metadata: 45 | name: dubbo2istio 46 | labels: 47 | app: dubbo2istio 48 | spec: 49 | selector: 50 | matchLabels: 51 | app: dubbo2istio 52 | replicas: 1 53 | template: 54 | metadata: 55 | labels: 56 | app: dubbo2istio 57 | spec: 58 | serviceAccountName: dubbo2istio 59 | containers: 60 | - name: dubbo2istio 61 | image: aeraki/dubbo2istio:${BUILD_TAG} 62 | imagePullPolicy: Never # Set ImagePullPolicy to Never to enforce MiniKube use local image 63 | env: 64 | - name: REGISTRY_ADDR 65 | value: "etcd:2379" 66 | - name: REGISTRY_TYPE 67 | value: "etcd" -------------------------------------------------------------------------------- /test/e2e/common/dubbo2istio-nacos.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dubbo2istio 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | labels: 11 | app: dubbo2istio 12 | name: dubbo2istio 13 | rules: 14 | - apiGroups: 15 | - networking.istio.io 16 | resources: 17 | - serviceentries 18 | verbs: 19 | - get 20 | - watch 21 | - list 22 | - update 23 | - patch 24 | - create 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | labels: 31 | app: dubbo2istio 32 | name: dubbo2istio 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: dubbo2istio 37 | subjects: 38 | - kind: ServiceAccount 39 | name: dubbo2istio 40 | namespace: meta-dubbo 41 | --- 42 | apiVersion: apps/v1 43 | kind: Deployment 44 | metadata: 45 | name: dubbo2istio 46 | labels: 47 | app: dubbo2istio 48 | spec: 49 | selector: 50 | matchLabels: 51 | app: dubbo2istio 52 | replicas: 1 53 | template: 54 | metadata: 55 | labels: 56 | app: dubbo2istio 57 | spec: 58 | serviceAccountName: dubbo2istio 59 | containers: 60 | - name: dubbo2istio 61 | image: aeraki/dubbo2istio:${BUILD_TAG} 62 | imagePullPolicy: Never # Set ImagePullPolicy to Never to enforce MiniKube use local image 63 | env: 64 | - name: REGISTRY_TYPE 65 | value: "nacos" 66 | - name: REGISTRY_ADDR 67 | value: "nacos:8848" -------------------------------------------------------------------------------- /test/e2e/common/dubbo2istio-zk.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: dubbo2istio 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRole 9 | metadata: 10 | labels: 11 | app: dubbo2istio 12 | name: dubbo2istio 13 | rules: 14 | - apiGroups: 15 | - networking.istio.io 16 | resources: 17 | - serviceentries 18 | verbs: 19 | - get 20 | - watch 21 | - list 22 | - update 23 | - patch 24 | - create 25 | - delete 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | labels: 31 | app: dubbo2istio 32 | name: dubbo2istio 33 | roleRef: 34 | apiGroup: rbac.authorization.k8s.io 35 | kind: ClusterRole 36 | name: dubbo2istio 37 | subjects: 38 | - kind: ServiceAccount 39 | name: dubbo2istio 40 | namespace: meta-dubbo 41 | --- 42 | apiVersion: apps/v1 43 | kind: Deployment 44 | metadata: 45 | name: dubbo2istio 46 | labels: 47 | app: dubbo2istio 48 | spec: 49 | selector: 50 | matchLabels: 51 | app: dubbo2istio 52 | replicas: 1 53 | template: 54 | metadata: 55 | labels: 56 | app: dubbo2istio 57 | spec: 58 | serviceAccountName: dubbo2istio 59 | containers: 60 | - name: dubbo2istio 61 | image: aeraki/dubbo2istio:${BUILD_TAG} 62 | imagePullPolicy: Never # Set ImagePullPolicy to Never to enforce MiniKube use local image 63 | env: 64 | - name: REGISTRY_ADDR 65 | value: "zookeeper:2181" 66 | - name: REGISTRY_TYPE 67 | value: "zookeeper" -------------------------------------------------------------------------------- /test/e2e/common/istio-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: install.istio.io/v1alpha1 3 | kind: IstioOperator 4 | spec: 5 | profile: default 6 | values: 7 | global: 8 | logging: 9 | level: default:debug 10 | meshConfig: 11 | accessLogFile: /dev/stdout 12 | defaultConfig: 13 | proxyMetadata: 14 | ISTIO_META_DNS_CAPTURE: "true" 15 | proxyStatsMatcher: 16 | inclusionPrefixes: 17 | - thrift 18 | - dubbo 19 | - kafka 20 | - meta_protocol 21 | inclusionRegexps: 22 | - .*dubbo.* 23 | - .*thrift.* 24 | - .*kafka.* 25 | - .*zookeeper.* 26 | - .*meta_protocol.* 27 | components: 28 | pilot: 29 | hub: istio 30 | tag: 1.10.0 31 | -------------------------------------------------------------------------------- /test/e2e/etcd/etcd_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package etcd 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/aeraki-mesh/dubbo2istio/test/e2e" 22 | 23 | "github.com/aeraki-mesh/dubbo2istio/test/e2e/util" 24 | ) 25 | 26 | func TestMain(m *testing.M) { 27 | setup() 28 | code := m.Run() 29 | shutdown() 30 | os.Exit(code) 31 | } 32 | 33 | func setup() { 34 | util.KubeApply("meta-dubbo", "../../../demo/k8s/aeraki-bootstrap-config.yaml", "") 35 | util.KubeApply("meta-dubbo", "../../../demo/k8s/etcd/etcd.yaml", "") 36 | util.LabelNamespace("dubbo", "istio-injection=enabled", "") 37 | util.KubeApply("meta-dubbo", "../../../demo/k8s/etcd/dubbo-example.yaml", "") 38 | } 39 | 40 | func shutdown() { 41 | // util.KubeDelete("dubbo", "../../../demo/k8s/etcd/etcd.yaml", "") 42 | // util.KubeDelete("dubbo", "../../../demo/k8s/etcd/dubbo-example.yaml", "") 43 | } 44 | 45 | func TestCreateServiceEntry(t *testing.T) { 46 | e2e.TestCreateServiceEntry(t) 47 | } 48 | 49 | func TestDeleteServiceEntry(t *testing.T) { 50 | e2e.TestDeleteServiceEntry(t) 51 | } 52 | -------------------------------------------------------------------------------- /test/e2e/nacos/nacos_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package nacos 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/aeraki-mesh/dubbo2istio/test/e2e" 22 | "github.com/aeraki-mesh/dubbo2istio/test/e2e/util" 23 | ) 24 | 25 | func TestMain(m *testing.M) { 26 | setup() 27 | code := m.Run() 28 | shutdown() 29 | os.Exit(code) 30 | } 31 | 32 | func setup() { 33 | util.KubeApply("meta-dubbo", "../../../demo/k8s/aeraki-bootstrap-config.yaml", "") 34 | util.KubeApply("meta-dubbo", "../../../demo/k8s/nacos/nacos.yaml", "") 35 | util.LabelNamespace("dubbo", "istio-injection=enabled", "") 36 | util.KubeApply("meta-dubbo", "../../../demo/k8s/nacos/dubbo-example.yaml", "") 37 | } 38 | 39 | func shutdown() { 40 | // util.KubeDelete("dubbo", "../../../demo/k8s/nacos/nacos.yaml", "") 41 | // util.KubeDelete("dubbo", "../../../demo/k8s/nacos/dubbo-example.yaml", "") 42 | } 43 | 44 | func TestCreateServiceEntry(t *testing.T) { 45 | e2e.TestCreateServiceEntry(t) 46 | } 47 | 48 | func TestDeleteServiceEntry(t *testing.T) { 49 | e2e.TestDeleteServiceEntry(t) 50 | } 51 | -------------------------------------------------------------------------------- /test/e2e/scripts/aeraki.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | export AERAKI_NAMESPACE="istio-system" 5 | export AERAKI_ISTIOD_ADDR="istiod.istio-system:15010" 6 | export AERAKI_IS_MASTER="true" 7 | export AERAKI_TAG="latest" 8 | 9 | BASEDIR=$(dirname "$0") 10 | 11 | kubectl create ns istio-system 12 | kubectl apply -f $BASEDIR/../common/aeraki-crd.yaml 13 | kubectl apply -f $BASEDIR/../common/aeraki.yaml -n ${AERAKI_NAMESPACE} 14 | -------------------------------------------------------------------------------- /test/e2e/scripts/dubbo2istio.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | export BUILD_TAG=`git log --format="%H" -n 1` 6 | 7 | BASEDIR=$(dirname "$0") 8 | 9 | if [ "$1" == zk ]; then 10 | envsubst < $BASEDIR/../common/dubbo2istio-zk.yaml > dubbo2istio.yaml 11 | elif [ "$1" == nacos ]; then 12 | envsubst < $BASEDIR/../common/dubbo2istio-nacos.yaml > dubbo2istio.yaml 13 | elif [ "$1" == etcd ]; then 14 | envsubst < $BASEDIR/../common/dubbo2istio-etcd.yaml > dubbo2istio.yaml 15 | fi 16 | 17 | kubectl create ns meta-dubbo 18 | kubectl apply -f dubbo2istio.yaml -n meta-dubbo -------------------------------------------------------------------------------- /test/e2e/scripts/istio.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is copied from github.com/apache/skywalking 4 | # ---------------------------------------------------------------------------- 5 | # Licensed to the Apache Software Foundation (ASF) under one 6 | # or more contributor license agreements. See the NOTICE file 7 | # distributed with this work for additional information 8 | # regarding copyright ownership. The ASF licenses this file 9 | # to you under the Apache License, Version 2.0 (the 10 | # "License"); you may not use this file except in compliance 11 | # with the License. You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, 16 | # software distributed under the License is distributed on an 17 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | # KIND, either express or implied. See the License for the 19 | # specific language governing permissions and limitations 20 | # under the License. 21 | # ---------------------------------------------------------------------------- 22 | 23 | set -ex 24 | 25 | istioctl version || (curl -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIO_VERSION sh - && sudo mv $PWD/istio-$ISTIO_VERSION/bin/istioctl /usr/local/bin/) 26 | istioctl install $@ 27 | kubectl label namespace default istio-injection=enabled --overwrite=true 28 | -------------------------------------------------------------------------------- /test/e2e/scripts/minikube.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is copied from https://github.com/apache/skywalking 4 | # ---------------------------------------------------------------------------- 5 | # Licensed to the Apache Software Foundation (ASF) under one 6 | # or more contributor license agreements. See the NOTICE file 7 | # distributed with this work for additional information 8 | # regarding copyright ownership. The ASF licenses this file 9 | # to you under the Apache License, Version 2.0 (the 10 | # "License"); you may not use this file except in compliance 11 | # with the License. You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, 16 | # software distributed under the License is distributed on an 17 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | # KIND, either express or implied. See the License for the 19 | # specific language governing permissions and limitations 20 | # under the License. 21 | # ---------------------------------------------------------------------------- 22 | 23 | set -x 24 | 25 | K8S_VER=${K8S_VER:-'k8s-v1.18.0'} 26 | 27 | function waitMinikube() { 28 | set +e 29 | kubectl cluster-info 30 | # This for loop waits until kubectl can access the api server that Minikube has created. 31 | for _ in {1..24}; do # Timeout for 240 seconds. 32 | kubectl get po --all-namespaces 33 | if [ $? -ne 1 ]; then 34 | break 35 | fi 36 | sleep 10 37 | done 38 | if ! kubectl get all --all-namespaces; then 39 | echo "Kubernetes failed to start" 40 | ps ax 41 | netstat -an 42 | docker images 43 | cat /var/lib/localkube/localkube.err 44 | printf '\n\n\n' 45 | kubectl cluster-info dump 46 | exit 1 47 | fi 48 | 49 | echo "Minikube is running" 50 | 51 | for _ in {1..6}; do # Timeout for 60 seconds. 52 | echo "$(sudo -E minikube ip) minikube.local" | sudo tee -a /etc/hosts 53 | ip=$(cat /etc/hosts | grep minikube.local | cut -d' ' -f1 | xargs) 54 | if [ -n "$ip" ]; then 55 | break 56 | fi 57 | sleep 10 58 | done 59 | 60 | ip=$(cat /etc/hosts | grep minikube.local | cut -d' ' -f1 | xargs) 61 | if [ -n "$ip" ]; then 62 | echo "minikube.local is mapped to $ip" 63 | else 64 | exit 1 65 | fi 66 | } 67 | 68 | # startMinikubeNone starts real kubernetes minikube with none driver. This requires `sudo`. 69 | function startMinikubeNone() { 70 | export MINIKUBE_WANTUPDATENOTIFICATION=false 71 | export MINIKUBE_WANTREPORTERRORPROMPT=false 72 | export MINIKUBE_HOME=$HOME 73 | export CHANGE_MINIKUBE_NONE_USER=true 74 | 75 | sudo -E minikube config set WantUpdateNotification false 76 | sudo -E minikube config set WantReportErrorPrompt false 77 | sudo -E minikube start --kubernetes-version=${K8S_VER#k8s-} --driver=none 78 | } 79 | 80 | function stopMinikube() { 81 | sudo minikube stop 82 | } 83 | 84 | case "$1" in 85 | start) startMinikubeNone ;; 86 | stop) stopMinikube ;; 87 | wait) waitMinikube ;; 88 | esac 89 | -------------------------------------------------------------------------------- /test/e2e/scripts/pre.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This file is copied from github.com/apache/skywalking 4 | # ---------------------------------------------------------------------------- 5 | # Licensed to the Apache Software Foundation (ASF) under one 6 | # or more contributor license agreements. See the NOTICE file 7 | # distributed with this work for additional information 8 | # regarding copyright ownership. The ASF licenses this file 9 | # to you under the Apache License, Version 2.0 (the 10 | # "License"); you may not use this file except in compliance 11 | # with the License. You may obtain a copy of the License at 12 | # 13 | # http://www.apache.org/licenses/LICENSE-2.0 14 | # 15 | # Unless required by applicable law or agreed to in writing, 16 | # software distributed under the License is distributed on an 17 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 | # KIND, either express or implied. See the License for the 19 | # specific language governing permissions and limitations 20 | # under the License. 21 | # ---------------------------------------------------------------------------- 22 | 23 | set -ex 24 | 25 | HELMVERSION=${HELMVERSION:-'helm-v3.0.0'} 26 | MINIKUBEVERESION=${MINIKUBEVERESION:-'minikube-v1.13.1'} 27 | K8SVERSION=${K8SVERSION:-'k8s-v1.19.2'} 28 | 29 | curl -sSL https://get.helm.sh/${HELMVERSION}-linux-amd64.tar.gz | \ 30 | sudo tar xz -C /usr/local/bin --strip-components=1 linux-amd64/helm 31 | 32 | sudo mkdir -p /usr/local/bin 33 | curl -sSL "https://storage.googleapis.com/minikube/releases/${MINIKUBEVERESION#minikube-}/minikube-linux-amd64" -o /tmp/minikube 34 | chmod +x /tmp/minikube 35 | sudo mv /tmp/minikube /usr/local/bin/minikube 36 | 37 | curl -sSL "https://storage.googleapis.com/kubernetes-release/release/${K8SVERSION#k8s-}/bin/linux/amd64/kubectl" -o /tmp/kubectl 38 | chmod +x /tmp/kubectl 39 | sudo mv /tmp/kubectl /usr/local/bin/kubectl 40 | 41 | sudo apt-get install -y socat conntrack 42 | 43 | -------------------------------------------------------------------------------- /test/e2e/util/common_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Istio Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "archive/tar" 19 | "compress/gzip" 20 | "context" 21 | "fmt" 22 | "io" 23 | "io/ioutil" 24 | "math/rand" 25 | "net/http" 26 | "os" 27 | "os/exec" 28 | "path/filepath" 29 | "runtime" 30 | "strings" 31 | "time" 32 | 33 | "github.com/google/go-github/github" 34 | "github.com/pkg/errors" 35 | 36 | "istio.io/istio/pkg/test/env" 37 | "istio.io/pkg/log" 38 | ) 39 | 40 | const ( 41 | releaseURL = "https://github.com/istio/istio/releases/download/%s/istio-%s-%s.tar.gz" 42 | ) 43 | 44 | // GetHeadCommitSHA finds the SHA of the commit to which the HEAD of branch points 45 | func GetHeadCommitSHA(org, repo, branch string) (string, error) { 46 | client := github.NewClient(nil) 47 | githubRefObj, _, err := client.Git.GetRef( 48 | context.Background(), org, repo, "refs/heads/"+branch) 49 | if err != nil { 50 | log.Warnf("Failed to get reference SHA of branch [%s]on repo [%s]\n", branch, repo) 51 | return "", err 52 | } 53 | return *githubRefObj.Object.SHA, nil 54 | } 55 | 56 | // WriteTextFile overwrites the file on the given path with content 57 | func WriteTextFile(filePath, content string) error { 58 | if len(content) > 0 && content[len(content)-1] != '\n' { 59 | content += "\n" 60 | } 61 | return ioutil.WriteFile(filePath, []byte(content), 0600) 62 | } 63 | 64 | // GitRootDir returns the absolute path to the root directory of the git repo 65 | // where this function is called 66 | func GitRootDir() (string, error) { 67 | dir, err := Shell("git rev-parse --show-toplevel") 68 | if err != nil { 69 | return "", err 70 | } 71 | return strings.Trim(dir, "\n"), nil 72 | } 73 | 74 | // Poll executes do() after time interval for a max of numTrials times. 75 | // The bool returned by do() indicates if polling succeeds in that trial 76 | func Poll(interval time.Duration, numTrials int, do func() (bool, error)) error { 77 | if numTrials < 0 { 78 | return fmt.Errorf("numTrials cannot be negative") 79 | } 80 | for i := 0; i < numTrials; i++ { 81 | if success, err := do(); err != nil { 82 | return fmt.Errorf("error during trial %d: %v", i, err) 83 | } else if success { 84 | return nil 85 | } else { 86 | time.Sleep(interval) 87 | } 88 | } 89 | return fmt.Errorf("max polling iteration reached") 90 | } 91 | 92 | // CreateTempfile creates a tempfile string. 93 | func CreateTempfile(tmpDir, prefix, suffix string) (string, error) { 94 | f, err := ioutil.TempFile(tmpDir, prefix) 95 | if err != nil { 96 | return "", err 97 | } 98 | var tmpName string 99 | if tmpName, err = filepath.Abs(f.Name()); err != nil { 100 | return "", err 101 | } 102 | if err = f.Close(); err != nil { 103 | return "", err 104 | } 105 | if err = os.Remove(tmpName); err != nil { 106 | log.Errorf("CreateTempfile unable to remove %s", tmpName) 107 | return "", err 108 | } 109 | return tmpName + suffix, nil 110 | } 111 | 112 | // WriteTempfile creates a tempfile with the specified contents. 113 | func WriteTempfile(tmpDir, prefix, suffix, contents string) (string, error) { 114 | fname, err := CreateTempfile(tmpDir, prefix, suffix) 115 | if err != nil { 116 | return "", err 117 | } 118 | 119 | if err := ioutil.WriteFile(fname, []byte(contents), 0644); err != nil { 120 | return "", err 121 | } 122 | return fname, nil 123 | } 124 | 125 | // Shell run command on shell and get back output and error if get one 126 | func Shell(format string, args ...interface{}) (string, error) { 127 | return sh(context.Background(), format, true, true, true, args...) 128 | } 129 | 130 | // ShellContext run command on shell and get back output and error if get one 131 | func ShellContext(ctx context.Context, format string, args ...interface{}) (string, error) { 132 | return sh(ctx, format, true, true, true, args...) 133 | } 134 | 135 | // ShellMuteOutput run command on shell and get back output and error if get one 136 | // without logging the output 137 | func ShellMuteOutput(format string, args ...interface{}) (string, error) { 138 | return sh(context.Background(), format, true, false, true, args...) 139 | } 140 | 141 | // ShellMuteOutputError run command on shell and get back output and error if get one 142 | // without logging the output or errors 143 | func ShellMuteOutputError(format string, args ...interface{}) (string, error) { 144 | return sh(context.Background(), format, true, false, false, args...) 145 | } 146 | 147 | // ShellSilent runs command on shell and get back output and error if get one 148 | // without logging the command or output. 149 | func ShellSilent(format string, args ...interface{}) (string, error) { 150 | return sh(context.Background(), format, false, false, false, args...) 151 | } 152 | 153 | func sh(ctx context.Context, format string, logCommand, logOutput, logError bool, args ...interface{}) (string, error) { 154 | command := fmt.Sprintf(format, args...) 155 | if logCommand { 156 | log.Infof("Running command %s", command) 157 | } 158 | c := exec.CommandContext(ctx, "sh", "-c", command) // #nosec 159 | bytes, err := c.CombinedOutput() 160 | if logOutput { 161 | if output := strings.TrimSuffix(string(bytes), "\n"); len(output) > 0 { 162 | log.Infof("Command output: \n%s", output) 163 | } 164 | } 165 | 166 | if err != nil { 167 | if logError { 168 | log.Infof("Command error: %v", err) 169 | } 170 | return string(bytes), fmt.Errorf("command failed: %q %v", string(bytes), err) 171 | } 172 | return string(bytes), nil 173 | } 174 | 175 | // RunBackground starts a background process and return the Process if succeed 176 | func RunBackground(format string, args ...interface{}) (*os.Process, error) { 177 | command := fmt.Sprintf(format, args...) 178 | log.Infoa("RunBackground: ", command) 179 | parts := strings.Split(command, " ") 180 | c := exec.Command(parts[0], parts[1:]...) // #nosec 181 | err := c.Start() 182 | if err != nil { 183 | log.Errorf("%s, command failed!", command) 184 | return nil, err 185 | } 186 | return c.Process, nil 187 | } 188 | 189 | // Record run command and record output into a file 190 | func Record(command, record string) error { 191 | resp, err := Shell(command) 192 | if err != nil { 193 | return err 194 | } 195 | err = ioutil.WriteFile(record, []byte(resp), 0600) 196 | return err 197 | } 198 | 199 | // GetOsExt returns the current OS tag. 200 | func GetOsExt() (string, error) { 201 | var osExt string 202 | switch runtime.GOOS { 203 | case "linux": 204 | osExt = "linux" 205 | case "darwin": 206 | osExt = "osx" 207 | default: 208 | return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS) 209 | } 210 | return osExt, nil 211 | } 212 | 213 | // CopyFile create a new file to src based on dst 214 | func CopyFile(src, dst string) error { 215 | var in, out *os.File 216 | var err error 217 | in, err = os.Open(src) 218 | if err != nil { 219 | return err 220 | } 221 | defer func() { 222 | if err = in.Close(); err != nil { 223 | log.Errorf("Error: close file from %s, %s", src, err) 224 | } 225 | }() 226 | out, err = os.Create(dst) 227 | if err != nil { 228 | return err 229 | } 230 | defer func() { 231 | if err = out.Close(); err != nil { 232 | log.Errorf("Error: close file from %s, %s", dst, err) 233 | } 234 | }() 235 | if _, err = io.Copy(out, in); err != nil { 236 | return err 237 | } 238 | err = out.Sync() 239 | return err 240 | } 241 | 242 | // GetResourcePath give "path from WORKSPACE", return absolute path at runtime 243 | func GetResourcePath(p string) string { 244 | return filepath.Join(env.IstioSrc, p) 245 | } 246 | 247 | // DownloadRelease gets the specified release from istio repo to tmpDir. 248 | func DownloadRelease(version, tmpDir string) (string, error) { 249 | err := os.Chdir(tmpDir) 250 | if err != nil { 251 | return "", err 252 | } 253 | osExt, err := GetOsExt() 254 | if err != nil { 255 | return "", err 256 | } 257 | url := fmt.Sprintf(releaseURL, version, version, osExt) 258 | fname := fmt.Sprintf("istio-%s.tar.gz", version) 259 | tgz := filepath.Join(tmpDir, fname) 260 | err = HTTPDownload(tgz, url) 261 | if err != nil { 262 | return "", err 263 | } 264 | f, err := os.Open(tgz) 265 | if err != nil { 266 | return "", err 267 | } 268 | err = ExtractTarGz(f, tmpDir) 269 | if err != nil { 270 | return "", err 271 | } 272 | subdir := "istio-" + version 273 | return filepath.Join(tmpDir, subdir), nil 274 | } 275 | 276 | const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 277 | 278 | // RandomString returns a random string of size n (letters only) 279 | func RandomString(n int) string { 280 | rand.Seed(time.Now().UnixNano()) 281 | b := make([]byte, n) 282 | for i := range b { 283 | b[i] = letters[rand.Int63()%int64(len(letters))] 284 | } 285 | return string(b) 286 | } 287 | 288 | // HTTPDownload download from src(url) and store into dst(local file) 289 | func HTTPDownload(dst string, src string) error { 290 | log.Infof("Start downloading from %s to %s ...\n", src, dst) 291 | var err error 292 | var out *os.File 293 | var resp *http.Response 294 | out, err = os.Create(dst) 295 | if err != nil { 296 | return err 297 | } 298 | defer func() { 299 | if err = out.Close(); err != nil { 300 | log.Errorf("Error: close file %s, %s", dst, err) 301 | } 302 | }() 303 | resp, err = http.Get(src) 304 | if err != nil { 305 | return err 306 | } 307 | defer func() { 308 | if err = resp.Body.Close(); err != nil { 309 | log.Errorf("Error: close downloaded file from %s, %s", src, err) 310 | } 311 | }() 312 | if resp.StatusCode != 200 { 313 | return fmt.Errorf("http get request, received unexpected response status: %s", resp.Status) 314 | } 315 | if _, err = io.Copy(out, resp.Body); err != nil { 316 | return err 317 | } 318 | log.Info("Download successfully!") 319 | return err 320 | } 321 | 322 | // ExtractTarGz extracts a .tar.gz file into current dir. 323 | func ExtractTarGz(gzippedStream io.Reader, dir string) error { 324 | uncompressedStream, err := gzip.NewReader(gzippedStream) 325 | if err != nil { 326 | return errors.Wrap(err, "Fail to uncompress") 327 | } 328 | tarReader := tar.NewReader(uncompressedStream) 329 | 330 | for { 331 | header, err := tarReader.Next() 332 | 333 | if err == io.EOF { 334 | break 335 | } 336 | 337 | if err != nil { 338 | return errors.Wrap(err, "ExtractTarGz: Next() failed") 339 | } 340 | 341 | rel := filepath.FromSlash(header.Name) 342 | abs := filepath.Join(dir, rel) 343 | 344 | switch header.Typeflag { 345 | case tar.TypeDir: 346 | if err := os.MkdirAll(abs, 0755); err != nil { 347 | return errors.Wrap(err, "ExtractTarGz: Mkdir() failed") 348 | } 349 | case tar.TypeReg: 350 | outFile, err := os.Create(abs) 351 | if err != nil { 352 | return errors.Wrap(err, "ExtractTarGz: Create() failed") 353 | } 354 | defer func() { _ = outFile.Close() }() 355 | if _, err := io.Copy(outFile, tarReader); err != nil { 356 | return errors.Wrap(err, "ExtractTarGz: Copy() failed") 357 | } 358 | default: 359 | return fmt.Errorf("unknown type: %s in %s", 360 | string(header.Typeflag), header.Name) 361 | } 362 | } 363 | return nil 364 | } 365 | -------------------------------------------------------------------------------- /test/e2e/util/retry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Istio Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "istio.io/pkg/log" 22 | ) 23 | 24 | const ( 25 | backoffFactor = 1.3 // backoff increases by this factor on each retry 26 | ) 27 | 28 | // Backoff returns a random value in [0, maxDelay] that increases exponentially with 29 | // retries, starting from baseDelay. It is the Go equivalent to C++'s 30 | // //util/time/backoff.cc. 31 | func Backoff(baseDelay, maxDelay time.Duration, retries int) time.Duration { 32 | backoff, max := float64(baseDelay), float64(maxDelay) 33 | for backoff < max && retries > 0 { 34 | backoff *= backoffFactor 35 | retries-- 36 | } 37 | if backoff > max { 38 | backoff = max 39 | } 40 | 41 | if backoff < 0 { 42 | return 0 43 | } 44 | return time.Duration(backoff) 45 | } 46 | 47 | // Retrier contains the retry configuration parameters. 48 | type Retrier struct { 49 | // BaseDelay is the minimum delay between retry attempts. 50 | BaseDelay time.Duration 51 | // MaxDelay is the maximum delay allowed between retry attempts. 52 | MaxDelay time.Duration 53 | // MaxDuration is the maximum cumulative duration allowed for all retries 54 | MaxDuration time.Duration 55 | // Retries defines number of retry attempts 56 | Retries int 57 | } 58 | 59 | // Break the retry loop if the error returned is of this type. 60 | type Break struct { 61 | Err error 62 | } 63 | 64 | func (e Break) Error() string { 65 | return e.Err.Error() 66 | } 67 | 68 | // Retry calls the given function a number of times, unless it returns a nil or a Break 69 | func (r Retrier) Retry(ctx context.Context, fn func(ctx context.Context, retryIndex int) error) (int, error) { 70 | if ctx == nil { 71 | ctx = context.Background() 72 | } 73 | if r.MaxDuration > 0 { 74 | var cancel context.CancelFunc 75 | ctx, cancel = context.WithTimeout(ctx, r.MaxDuration) 76 | defer cancel() 77 | } 78 | 79 | var err error 80 | var i int 81 | if r.Retries <= 0 { 82 | log.Warnf("retries must to be >= 1. Got %d, setting to 1", r.Retries) 83 | r.Retries = 1 84 | } 85 | for i = 1; i <= r.Retries; i++ { 86 | err = fn(ctx, i) 87 | if err == nil { 88 | return i, nil 89 | } 90 | if be, ok := err.(Break); ok { 91 | return i, be.Err 92 | } 93 | 94 | select { 95 | case <-ctx.Done(): 96 | return i - 1, ctx.Err() 97 | case <-time.After(Backoff(r.BaseDelay, r.MaxDelay, i)): 98 | } 99 | } 100 | return i - 1, err 101 | } 102 | -------------------------------------------------------------------------------- /test/e2e/zk/zk_test.go: -------------------------------------------------------------------------------- 1 | // Copyright Aeraki Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zk 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/aeraki-mesh/dubbo2istio/test/e2e" 22 | 23 | "github.com/aeraki-mesh/dubbo2istio/test/e2e/util" 24 | ) 25 | 26 | func TestMain(m *testing.M) { 27 | setup() 28 | code := m.Run() 29 | shutdown() 30 | os.Exit(code) 31 | } 32 | 33 | func setup() { 34 | util.KubeApply("meta-dubbo", "../../../demo/k8s/aeraki-bootstrap-config.yaml", "") 35 | util.KubeApply("meta-dubbo", "../../../demo/k8s/zk/zookeeper.yaml", "") 36 | util.LabelNamespace("dubbo", "istio-injection=enabled", "") 37 | util.KubeApply("meta-dubbo", "../../../demo/k8s/zk/dubbo-example.yaml", "") 38 | } 39 | 40 | func shutdown() { 41 | //util.KubeDelete("dubbo", "../../../demo/k8s/zk/zookeeper.yaml", "") 42 | //util.KubeDelete("dubbo", "../../../demo/k8s/zk/dubbo-example.yaml", "") 43 | } 44 | 45 | func TestCreateServiceEntry(t *testing.T) { 46 | e2e.TestCreateServiceEntry(t) 47 | } 48 | 49 | func TestDeleteServiceEntry(t *testing.T) { 50 | e2e.TestDeleteServiceEntry(t) 51 | } 52 | --------------------------------------------------------------------------------