Compare commits
321 Commits
Author | SHA1 | Date |
---|---|---|
|
0c35e096c2 | |
|
142a36f608 | |
|
b18afb95c1 | |
|
aa87b24e1a | |
|
fdc51ba605 | |
|
ce6249142f | |
|
26c8b307d7 | |
|
6585b217bc | |
|
b609c729c3 | |
|
432ced4e0e | |
|
116116c8c4 | |
|
ca08bd7d6e | |
|
81610e505a | |
|
17439be724 | |
|
91999fbdcb | |
|
6645b00f24 | |
|
acb7b49551 | |
|
ba416d1496 | |
|
ff5e484997 | |
|
b900c28347 | |
|
5d17242ffc | |
|
39357660ab | |
|
49abb7c286 | |
|
8185feca1d | |
|
5789d4d774 | |
|
2fdf44d308 | |
|
5941bfb9bc | |
|
0c24dcb0a2 | |
|
a1d683a7c9 | |
|
be65db1892 | |
|
eb93fdd52a | |
|
e7e52acfa7 | |
|
02838da959 | |
|
0e73a0864c | |
|
61d347609e | |
|
a9df169b5a | |
|
98efe26906 | |
|
384a9cc1bb | |
|
0ffdca3e0b | |
|
5ae4071508 | |
|
4082efa1fc | |
|
fc03e6483c | |
|
2b8d29a0ab | |
|
881c11d51f | |
|
855922964e | |
|
9e814d2d43 | |
|
cdfbebf407 | |
|
017cacf12c | |
|
ab4a4bbb92 | |
|
853119b615 | |
|
f99398c649 | |
|
a61a79aca9 | |
|
00570523fa | |
|
508a912c35 | |
|
3ee0d33fff | |
|
03e4419157 | |
|
3998dfedf1 | |
|
1ffec6a785 | |
|
0fad6ef8cc | |
|
46b3590fb6 | |
|
a2d870e15e | |
|
758b18cb7d | |
|
d630fb16b4 | |
|
7c6f889e72 | |
|
0826dc9c15 | |
|
96111de3da | |
|
137fdee7f1 | |
|
c4200d7516 | |
|
30b331b4b0 | |
|
8fceffde07 | |
|
8dde539a79 | |
|
1cd2e3b408 | |
|
ee3040f011 | |
|
c5d574e357 | |
|
eb3e74a678 | |
|
1f16efb043 | |
|
467752e355 | |
|
91bf0d93f8 | |
|
9cab680e46 | |
|
c62e93a677 | |
|
e3354fefb0 | |
|
57e03c8aee | |
|
f1c1c00190 | |
|
4fced18886 | |
|
bdfa05670e | |
|
d389587d96 | |
|
e5283d2a23 | |
|
e65fba55a0 | |
|
b17f6fff8d | |
|
8748f0c2be | |
|
c38eed392d | |
|
070ac65f78 | |
|
a48db6ec63 | |
|
a6f8708aa0 | |
|
98fad6eb9d | |
|
0c1f7de7dc | |
|
5c1aea3681 | |
|
1b39a45f6c | |
|
909c482228 | |
|
a4e453b1fd | |
|
95e535ff84 | |
|
bc98df3c15 | |
|
247d0ff08c | |
|
d4b0335b50 | |
|
ef88f2201c | |
|
57e0c00ca5 | |
|
ad8d7aede4 | |
|
83903aca2a | |
|
ba7be8fff0 | |
|
1d38518d82 | |
|
860a892391 | |
|
a0da72a631 | |
|
e166342476 | |
|
edfb25e035 | |
|
6994fcde13 | |
|
03e307bb1a | |
|
b386a5a140 | |
|
1cf0a7f1ee | |
|
b353bd657d | |
|
256800ecc1 | |
|
e3dba5e920 | |
|
7f33ea54f1 | |
|
1225addc02 | |
|
1661721f91 | |
|
d89e8be910 | |
|
88832f62a6 | |
|
2a3cc7e7ac | |
|
b8dc1cfc2d | |
|
a1c131464e | |
|
e777c60dae | |
|
aa55f6f10b | |
|
f1ac50cd50 | |
|
76ea013045 | |
|
1daaa87c88 | |
|
98c724e960 | |
|
b9a2cc4269 | |
|
41743e6eac | |
|
6c19464e10 | |
|
457bb60ae2 | |
|
87a3d8664f | |
|
4a1e547f22 | |
|
2d514b5964 | |
|
5b05f5089e | |
|
7c6de38711 | |
|
896021a3a6 | |
|
03c6d9fcbd | |
|
e7886fa195 | |
|
acabacce74 | |
|
a414608331 | |
|
a4437a25a1 | |
|
9aa2d1f551 | |
|
dd65185cf1 | |
|
664debbab0 | |
|
daf093574c | |
|
4e1f60fe7c | |
|
8c36d5f82e | |
|
9996a93ce7 | |
|
4a6df3be28 | |
|
f97e6a129c | |
|
543a7c9509 | |
|
1f804a8587 | |
|
d6f98bbec2 | |
|
0110ed725e | |
|
c2a60a8b2b | |
|
40c7e098f5 | |
|
c3f01fb4f8 | |
|
c1bb1f6590 | |
|
da636462f7 | |
|
4f15a6217a | |
|
f635e0ef1e | |
|
2c08639df9 | |
|
9a79110b8e | |
|
ad5e10fccb | |
|
81ff12b615 | |
|
899a8f452a | |
|
e1ea0a409c | |
|
20c551954e | |
|
69a175d712 | |
|
e7a90a1be7 | |
|
357fa3f205 | |
|
d8c66afab4 | |
|
fc660bf219 | |
|
d501042f6e | |
|
a006d5abe5 | |
|
5cb1128216 | |
|
89a8d5c6eb | |
|
ef12b9bfac | |
|
cb7071f25a | |
|
853767e3ee | |
|
c4305c11df | |
|
7aa51bd4fb | |
|
0bd70a4cf1 | |
|
2b0b1af2e8 | |
|
410bed774d | |
|
489e2185d8 | |
|
95919f93b2 | |
|
479b2d07f2 | |
|
4f5894a0b1 | |
|
60d57e89e2 | |
|
861ce04ddd | |
|
0acb811025 | |
|
0210fe078d | |
|
f28cd0b60c | |
|
7c6f6e1bd4 | |
|
bd4ece9fcb | |
|
71d69822ab | |
|
54edcf1e7d | |
|
79dd85b76d | |
|
85063676de | |
|
29fc69ff9e | |
|
d62da32b02 | |
|
ed9f62b0de | |
|
ecd9d4c064 | |
|
cf79ef16b3 | |
|
0f6ec88966 | |
|
c5a411787b | |
|
94324b88fb | |
|
10677202c5 | |
|
c309b91d63 | |
|
651454cf89 | |
|
cfa885138b | |
|
17cd7161c8 | |
|
3064410f28 | |
|
7ffefc0bb1 | |
|
a00dc097e5 | |
|
961b5f3ad6 | |
|
48e7e8f2d3 | |
|
35e7164eb9 | |
|
c732c4a790 | |
|
2e6c2cce01 | |
|
7237ef9465 | |
|
847e4938dc | |
|
ddd6aa8748 | |
|
7e94c13918 | |
|
69b48df8c5 | |
|
8d2fecf793 | |
|
50c731f6ff | |
|
0b4fe40d4d | |
|
a09c564a8f | |
|
1a8bdf9850 | |
|
72ff493469 | |
|
1937316b00 | |
|
bc734032aa | |
|
b0fa9372db | |
|
d85d247452 | |
|
8b2077da97 | |
|
a720379f0c | |
|
e4cef17fa7 | |
|
a11940ad67 | |
|
6413d19ee8 | |
|
f58d0104e5 | |
|
df18e6665f | |
|
3a421f565f | |
|
ea920079a7 | |
|
f445eec060 | |
|
3ab307d000 | |
|
f3c0ac3965 | |
|
53f385d62e | |
|
66ee96c060 | |
|
ee7c8dcb90 | |
|
b342e32d95 | |
|
fb97bae4c3 | |
|
727d062bb7 | |
|
7413cf2f28 | |
|
ee4ec0e4c8 | |
|
3012a02323 | |
|
d7b540897e | |
|
ec8948748f | |
|
3569481266 | |
|
8db3e8c243 | |
|
cd2b981a7e | |
|
3defb3490a | |
|
4ffc14ab63 | |
|
dac985047f | |
|
49fba56703 | |
|
f90b4a1c31 | |
|
ecc74f903e | |
|
9f3e9355dc | |
|
3272914f72 | |
|
c58ec08d2a | |
|
55a42d4954 | |
|
801436774b | |
|
769a181e43 | |
|
48c20dd421 | |
|
a8c7211748 | |
|
9896807332 | |
|
b4560e5ddc | |
|
693fe3bc34 | |
|
91bc50cdc8 | |
|
c5c9ac0659 | |
|
d91e7e41e7 | |
|
e6980cec19 | |
|
7ade3c5e3b | |
|
85d48c20ca | |
|
954846a1e8 | |
|
6e34dfb851 | |
|
94bc8727ae | |
|
57e60d2fc9 | |
|
f7320d29f3 | |
|
9dbb59d86d | |
|
800cc493ad | |
|
216b0ec6fa | |
|
31cb8c8a01 | |
|
09ecbe2cd2 | |
|
ad33285e04 | |
|
8db18d1104 | |
|
f8375f9f55 | |
|
0618fb6879 | |
|
f223d3d8cd | |
|
2e0448fc62 | |
|
c670da7d46 | |
|
874c7b4c1f | |
|
6fff8e514a | |
|
7af8b35424 | |
|
c185d10a26 | |
|
7a703352c5 | |
|
dc45639f68 | |
|
00c93b7234 | |
|
9750f988e9 | |
|
25919e0dfa | |
|
f2f3dbc5a6 |
|
@ -11,3 +11,4 @@ target
|
|||
node_modules
|
||||
test/derby.log
|
||||
/console-fe
|
||||
.flattened-pom.xml
|
||||
|
|
130
README.md
130
README.md
|
@ -1,9 +1,11 @@
|
|||
# NacosSync
|
||||
# Nacos Sync
|
||||
|
||||
## [Example](https://github.com/paderlol/nacos-sync-example)
|
||||
|
||||
## Function
|
||||
|
||||
- Console: provide API and console for management
|
||||
- Worker: provider the service registration synchronization.
|
||||
- Worker: provide the service registration synchronization.
|
||||
|
||||
## Architecture
|
||||
|
||||
|
@ -43,6 +45,128 @@ Info | +------------+ ^
|
|||
|
||||
|
||||
## Quick Start:
|
||||
- Swagger API: http://127.0.0.1:8081/swagger-ui.html#/
|
||||
- Swagger API: http://127.0.0.1:8083/swagger-ui.html#/
|
||||
- Web Console: http://127.0.0.1:8083/
|
||||
- Others: TBD
|
||||
|
||||
# NacosSync Migration User Guide
|
||||
|
||||
## Support migration type
|
||||
|
||||
| Source | Target | Support | Note |
|
||||
| --------- | --------- | ------- | ------------------------------------------------------------ |
|
||||
| Nacos | Nacos | Yes | Only supports the same version of Nacos migration,**especially** the version of **0.8** migrates to 1.0 or above. |
|
||||
| Nacos | Zookeeper | Yes | Only support registery center of **Dubbo** |
|
||||
| Nacos | Consul | Yes | Only support registery center of **Spring Cloud** |
|
||||
| Nacos | Eureka | Yes | Only support registery center of **Spring Cloud** |
|
||||
| Zookeeper | Nacos | Yes | Only support registery center of **Dubbo** |
|
||||
| Consul | Nacos | Yes | Only support registery center of **Spring Cloud** |
|
||||
| Eureka | Nacos | Yes | Only support registery center of **Spring Cloud** |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Manual Goal
|
||||
|
||||
- Start the NacosSync service
|
||||
- Use a simple example to demonstrate how to migrate a Dubbo client registered in the Zookeeper Registry to the Nacos Registry
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, install the following:
|
||||
|
||||
- 64bit OS: Linux/Unix/Mac/Windows supported, Linux/Unix/Mac recommended.
|
||||
- 64bit JDK 1.8+: downloads, JAVA_HOME settings.
|
||||
- Maven 3.5.2+: [downloads](https://maven.apache.org/download.cgi), [settings](https://maven.apache.org/settings.html).
|
||||
- MySql 5.6.+
|
||||
|
||||
## Download & Build From Release
|
||||
|
||||
There are two ways to get NacosSync.
|
||||
|
||||
- Download run package
|
||||
- Download source code from Github
|
||||
|
||||
``` xml
|
||||
|
||||
cd nacos-sync/
|
||||
mvn clean package -U
|
||||
|
||||
```
|
||||
|
||||
The path to the target file:
|
||||
|
||||
``` xml
|
||||
|
||||
nacos-sync/nacossync-distribution/target/nacos-sync-0.5.0.tar.gz
|
||||
|
||||
```
|
||||
|
||||
After extracting the installation package, the directory structure:
|
||||
|
||||
``` xml
|
||||
|
||||
nacos-sync
|
||||
├── LICENSE
|
||||
├── NOTICE
|
||||
├── bin
|
||||
│ ├── nacosSync.sql
|
||||
│ ├── shutdown.sh
|
||||
│ └── startup.sh
|
||||
├── conf
|
||||
│ ├── application.properties
|
||||
│ └── logback-spring.xml
|
||||
├── logs
|
||||
└── nacos-sync-server.jar
|
||||
|
||||
```
|
||||
|
||||
## Initialize The DB
|
||||
|
||||
The default is Mysql database, which can support other relational databases
|
||||
|
||||
- Build db schema, the default schema name nacos_sync.
|
||||
- Tables do not need to be created separately, which is conducive to hibernate's automatic table creation function.
|
||||
- If the automatic table creation fails, you can build the table nacosSync.sql, the table statement is in the bin folder.
|
||||
|
||||
## DB Configuration
|
||||
|
||||
In the bin folder, application.properties:
|
||||
|
||||
``` xml
|
||||
|
||||
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/nacos_sync?characterEncoding=utf8
|
||||
spring.datasource.username=root
|
||||
spring.datasource.password=root
|
||||
|
||||
```
|
||||
|
||||
## Start Server
|
||||
|
||||
``` xml
|
||||
|
||||
$ nacosSync/bin:
|
||||
sh startup.sh start
|
||||
|
||||
```
|
||||
|
||||
## Admin Console
|
||||
|
||||
``` xml
|
||||
|
||||
http://127.0.0.1:8083/#/serviceSync
|
||||
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Full Synchronization from Zookeeper to Nacos (Dubbo)
|
||||
When “*” is entered in the “Service Name” field of this form, it will fully synchronize all services from Zookeeper to Nacos, but only when using Dubbo.
|
||||

|
||||
|
||||
### Full Synchronization from Nacos to Nacos
|
||||
When “All” is entered in the “Service Name” field of this form, it will automatically synchronize all registered services within the **default group** of the current cluster.
|
||||
|
||||
This description explains the functionality clearly for English-speaking users.
|
||||

|
|
@ -16,7 +16,7 @@
|
|||
<parent>
|
||||
<artifactId>nacossync-parent</artifactId>
|
||||
<groupId>com.alibaba.nacossync</groupId>
|
||||
<version>0.1.0</version>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -27,5 +27,4 @@
|
|||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
|
@ -1,11 +1,20 @@
|
|||
{
|
||||
"presets": [
|
||||
"env",
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"useBuiltIns": "entry"
|
||||
}
|
||||
],
|
||||
"react-app"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-react-es6-displayname",
|
||||
"transform-decorators-legacy",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
"legacy": true
|
||||
}
|
||||
],
|
||||
[
|
||||
"babel-plugin-import",
|
||||
{
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "eslint-config-ali/react",
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"globals": {
|
||||
"GLOBAL": true
|
||||
},
|
||||
"rules": {
|
||||
"max-len": 0,
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-no-target-blank": 0
|
||||
}
|
||||
}
|
|
@ -20,6 +20,11 @@ module.exports = {
|
|||
"css-loader",
|
||||
"sass-loader"
|
||||
]
|
||||
}, {
|
||||
enforce: 'pre',
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
}, {
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = Object.assign({}, base, {
|
|||
context: ['/nacossync'],
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
target: 'http://10.101.15.150:8081'
|
||||
target: 'http://30.5.121.153:8081'
|
||||
}],
|
||||
disableHostCheck: true
|
||||
},
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Nacos-Sync</title>
|
||||
<link rel="shortcut icon" href="//www.aliyun.com/favicon.ico" type="image/x-icon">
|
||||
<link href="./css/main.e2917886.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="text/javascript" src="./js/main.b73436b5.js"></script></body>
|
||||
</html>
|
|
@ -4,6 +4,9 @@
|
|||
"description": "console fe",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"eslint": "eslint src/",
|
||||
"eslint-output": "eslint -f html src/ > eslint-result.html",
|
||||
"eslint-fix": "eslint --fix src/",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.conf.js --open",
|
||||
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.conf.js && node build/copy-dist.js"
|
||||
},
|
||||
|
@ -14,39 +17,43 @@
|
|||
"url": "git+https://github.com/nacos-group/nacos-sync.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-plugin-import": "^1.10.0",
|
||||
"babel-plugin-transform-decorators": "^6.24.1",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.5",
|
||||
"babel-plugin-transform-react-es6-displayname": "^1.0.0-beta1.4",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-react-app": "^3.1.1",
|
||||
"babel-runtime": "^6.23.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"@babel/cli": "^7.2.3",
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/plugin-proposal-decorators": "^7.2.3",
|
||||
"@babel/preset-env": "^7.2.3",
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-plugin-import": "^1.11.0",
|
||||
"babel-preset-react-app": "^6.1.0",
|
||||
"clean-webpack-plugin": "^1.0.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"css-loader": "^2.0.2",
|
||||
"eslint": "^5.11.0",
|
||||
"eslint-config-ali": "^4.1.0",
|
||||
"eslint-loader": "^2.1.1",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"file-loader": "^3.0.1",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "^0.4.3",
|
||||
"node-sass": "^4.9.3",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.0",
|
||||
"uglifyjs-webpack-plugin": "^2.0.1",
|
||||
"url-loader": "^1.1.1",
|
||||
"webpack": "^4.20.2",
|
||||
"style-loader": "^0.23.1",
|
||||
"uglifyjs-webpack-plugin": "^2.1.0",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.28.2",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.1.9"
|
||||
"webpack-dev-server": "^3.1.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alifd/next": "^1.7.6",
|
||||
"@alifd/next": "^1.11.5",
|
||||
"axios": "^0.18.0",
|
||||
"moment": "^2.22.2",
|
||||
"react": "^16.6.0",
|
||||
"react-dom": "^16.6.0",
|
||||
"react-redux": "^5.1.0",
|
||||
"moment": "^2.23.0",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-redux": "^5.1.1",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-router-redux": "^4.0.8",
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import React from 'react'
|
||||
import './index.scss'
|
||||
import React from 'react';
|
||||
import './index.scss';
|
||||
|
||||
class FuncHead extends React.Component {
|
||||
render() {
|
||||
const {title, subtitle} = this.props
|
||||
return (
|
||||
<div className="function-header">
|
||||
<h4 className="main-title">{title}</h4>
|
||||
{
|
||||
subtitle ? <span className="subtitle">{subtitle}</span> : null
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
const { title, subtitle } = this.props;
|
||||
return (
|
||||
<div className="function-header">
|
||||
<h4 className="main-title">{title}</h4>
|
||||
{subtitle && <span className="subtitle">{subtitle}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FuncHead
|
||||
export default FuncHead;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import FuncHead from './FuncHead'
|
||||
import FuncHead from './FuncHead';
|
||||
|
||||
export default FuncHead
|
||||
export default FuncHead;
|
||||
|
|
|
@ -1,65 +1,62 @@
|
|||
import React from 'react'
|
||||
import {connect} from "react-redux"
|
||||
import {ConfigProvider} from '@alifd/next'
|
||||
import LogoImage from './images/logo.svg'
|
||||
import {changeLanguage} from '../../reducers/locale'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ConfigProvider } from '@alifd/next';
|
||||
import LogoImage from './images/logo.svg';
|
||||
import { changeLanguage } from '../../reducers/locale';
|
||||
|
||||
import './index.scss'
|
||||
import './index.scss';
|
||||
|
||||
@connect(state => ({...state.locale}), {changeLanguage})
|
||||
@connect(state => ({ ...state.locale }), { changeLanguage })
|
||||
@ConfigProvider.config
|
||||
class Header extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
static displayName = 'Header'
|
||||
|
||||
languageSwitching() {
|
||||
const {language = 'en-US', changeLanguage} = this.props
|
||||
changeLanguage(language === 'en-US' ? 'zh-CN' : 'en-US')
|
||||
}
|
||||
languageSwitching() {
|
||||
const { language = 'en-US' } = this.props;
|
||||
this.props.changeLanguage(language === 'en-US' ? 'zh-CN' : 'en-US');
|
||||
}
|
||||
|
||||
render() {
|
||||
const {locale = {}, language = 'en-US'} = this.props
|
||||
const {home, docs, blog, community, languageSwitchButton} = locale
|
||||
const BASE_URL = `https://nacos.io/${language.toLocaleLowerCase()}/`
|
||||
const NAV_MENU = [{
|
||||
id: 1,
|
||||
title: home,
|
||||
link: BASE_URL
|
||||
}, {
|
||||
id: 2,
|
||||
title: docs,
|
||||
link: `${BASE_URL}docs/what-is-nacos.html`
|
||||
}, {
|
||||
id: 3,
|
||||
title: blog,
|
||||
link: `${BASE_URL}blog/index.html`
|
||||
}, {
|
||||
id: 4,
|
||||
title: community,
|
||||
link: `${BASE_URL}community/index.html`
|
||||
}]
|
||||
return (
|
||||
<header className="header-container-primary">
|
||||
<a href="/" className="logo" title="Nacos-Sync">
|
||||
<img src={LogoImage} className="logo-img"/>
|
||||
</a>
|
||||
<span
|
||||
className="language-switch language-switch-primary"
|
||||
onClick={() => this.languageSwitching()}
|
||||
>{languageSwitchButton}</span>
|
||||
<ul className="nav-menu">
|
||||
{
|
||||
NAV_MENU.map(item => (
|
||||
<li key={item.id}>
|
||||
<a href={item.link} target="_blank">{item.title}</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
const { locale = {}, language = 'en-US' } = this.props;
|
||||
const { home, docs, blog, community, languageSwitchButton } = locale;
|
||||
const BASE_URL = `https://nacos.io/${language.toLocaleLowerCase()}/`;
|
||||
const NAV_MENU = [{
|
||||
id: 1,
|
||||
title: home,
|
||||
link: BASE_URL,
|
||||
}, {
|
||||
id: 2,
|
||||
title: docs,
|
||||
link: `${BASE_URL}docs/what-is-nacos.html`,
|
||||
}, {
|
||||
id: 3,
|
||||
title: blog,
|
||||
link: `${BASE_URL}blog/index.html`,
|
||||
}, {
|
||||
id: 4,
|
||||
title: community,
|
||||
link: `${BASE_URL}community/index.html`,
|
||||
}];
|
||||
return (
|
||||
<header className="header-container-primary">
|
||||
<a href="/" className="logo" title="Nacos-Sync">
|
||||
<img src={LogoImage} className="logo-img" />
|
||||
</a>
|
||||
<span className="language-switch language-switch-primary" onClick={() => this.languageSwitching()}>
|
||||
{languageSwitchButton}
|
||||
</span>
|
||||
<ul className="nav-menu">
|
||||
{
|
||||
NAV_MENU.map(item => (
|
||||
<li key={item.id}>
|
||||
<a href={item.link} target="_blank">{item.title}</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Header
|
||||
export default Header;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import Header from './Header'
|
||||
import Header from './Header';
|
||||
|
||||
export default Header
|
||||
export default Header;
|
||||
|
|
|
@ -1,67 +1,68 @@
|
|||
import React from 'react'
|
||||
import {withRouter} from 'react-router-dom'
|
||||
import {ConfigProvider, Nav} from '@alifd/next'
|
||||
import './index.scss'
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { ConfigProvider, Nav } from '@alifd/next';
|
||||
import './index.scss';
|
||||
|
||||
const {Item, SubNav} = Nav
|
||||
const { Item, SubNav } = Nav;
|
||||
|
||||
@withRouter
|
||||
@ConfigProvider.config
|
||||
class Menu extends React.Component {
|
||||
static displayName = 'Menu'
|
||||
static displayName = 'Menu'
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
_getSubNav(menuItem) {
|
||||
return (
|
||||
<SubNav label={menuItem.title} key={menuItem.id}>
|
||||
{menuItem.child.map(item => (item.child ? this._getSubNav(item) : this._getItem(item)))}
|
||||
</SubNav>
|
||||
);
|
||||
}
|
||||
|
||||
_getSubNav(menuItem) {
|
||||
return (
|
||||
<SubNav label={menuItem.title} key={menuItem.id}>
|
||||
{menuItem.child.map(item => item.child ? this._getSubNav(item) : this._getItem(item))}
|
||||
</SubNav>
|
||||
)
|
||||
}
|
||||
_getItem(menuItem) {
|
||||
return (
|
||||
<Item
|
||||
key={menuItem.id}
|
||||
onSelect={() => {
|
||||
if (this.props.location.pathname === menuItem.link) return;
|
||||
this.props.history.push(menuItem.link);
|
||||
}}
|
||||
>
|
||||
{menuItem.title}
|
||||
</Item>
|
||||
);
|
||||
}
|
||||
|
||||
_getItem(menuItem) {
|
||||
return <Item
|
||||
key={menuItem.id}
|
||||
onSelect={() => {
|
||||
if (this.props.location.pathname === menuItem.link) return
|
||||
this.props.history.push(menuItem.link)
|
||||
}}
|
||||
>{menuItem.title}</Item>
|
||||
}
|
||||
generateMenu() {
|
||||
return this.buildMenuData().map(item => (item.child ? this._getSubNav(item) : this._getItem(item)));
|
||||
}
|
||||
|
||||
generateMenu() {
|
||||
return this.buildMenuData().map(item => (item.child ? this._getSubNav(item) : this._getItem(item)))
|
||||
}
|
||||
buildMenuData() {
|
||||
const { locale = {} } = this.props;
|
||||
// const { serviceSync, clusterConfig, systemConfig } = locale;
|
||||
const { serviceSync, clusterConfig } = locale;
|
||||
return [{
|
||||
id: 1,
|
||||
title: serviceSync,
|
||||
link: '/serviceSync',
|
||||
}, {
|
||||
id: 2,
|
||||
title: clusterConfig,
|
||||
link: '/clusterConfig',
|
||||
}];
|
||||
// , {
|
||||
// id: 3,
|
||||
// title: systemConfig,
|
||||
// link: '/systemConfig',
|
||||
// }];
|
||||
}
|
||||
|
||||
buildMenuData() {
|
||||
const {locale = {}} = this.props
|
||||
const {serviceSync, clusterConfig, systemConfig} = locale
|
||||
return [{
|
||||
id: 1,
|
||||
title: serviceSync,
|
||||
link: '/serviceSync'
|
||||
}, {
|
||||
id: 2,
|
||||
title: clusterConfig,
|
||||
link: '/clusterConfig'
|
||||
}, {
|
||||
id: 3,
|
||||
title: systemConfig,
|
||||
link: '/systemConfig'
|
||||
}]
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="menu">
|
||||
<Nav openMode="multiple">{this.generateMenu()}</Nav>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="menu">
|
||||
<Nav openMode="multiple">{this.generateMenu()}</Nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Menu
|
||||
export default Menu;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import Menu from './Menu'
|
||||
import Menu from './Menu';
|
||||
|
||||
export default Menu
|
||||
export default Menu;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
export const LANGUAGE_KEY = 'site_language'
|
||||
export const LANGUAGE_SWITCH = 'LANGUAGE_SWITCH'
|
||||
export const LANGUAGE_KEY = 'site_language';
|
||||
export const LANGUAGE_SWITCH = 'LANGUAGE_SWITCH';
|
||||
|
||||
export const SUCCESS_RESULT_CODE = 'SUCCESS'
|
||||
export const SUCCESS_RESULT_CODE = 'SUCCESS';
|
||||
|
||||
export const CLUSTER_LIST = 'CLUSTER_LIST'
|
||||
export const GET_CLUSTER_BY_ID = 'GET_CLUSTER_BY_ID'
|
||||
export const CLUSTER_TYPES = 'CLUSTER_TYPES'
|
||||
export const CLUSTER_LIST = 'CLUSTER_LIST';
|
||||
export const GET_CLUSTER_BY_ID = 'GET_CLUSTER_BY_ID';
|
||||
export const CLUSTER_TYPES = 'CLUSTER_TYPES';
|
||||
|
||||
export const TASK_LIST = 'TASK_LIST'
|
||||
export const GET_TASK_BY_ID = 'GET_TASK_BY_ID'
|
||||
export const TASK_LIST = 'TASK_LIST';
|
||||
export const GET_TASK_BY_ID = 'GET_TASK_BY_ID';
|
||||
|
||||
export const PAGE_SIZE = 10
|
||||
export const PAGE_SIZE = 10;
|
||||
|
||||
export const REDUX_DEVTOOLS = '__REDUX_DEVTOOLS_EXTENSION__';
|
||||
|
|
|
@ -1,85 +1,123 @@
|
|||
import React from 'react'
|
||||
import {Form, Input, Select, Dialog, ConfigProvider} from '@alifd/next'
|
||||
import {connect} from 'react-redux'
|
||||
import {getTypes, add} from '../../reducers/cluster'
|
||||
import '../../style/dialog-form.scss'
|
||||
import React from 'react';
|
||||
import { Form, Input, Select, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getTypes, add } from '../../reducers/cluster';
|
||||
import '../../style/dialog-form.scss';
|
||||
|
||||
const FormItem = Form.Item
|
||||
const {Option} = Select
|
||||
const FormItem = Form.Item;
|
||||
const { Option } = Select;
|
||||
|
||||
@connect(state => ({...state.cluster}), {getTypes}, null, {withRef: true})
|
||||
@connect(state => ({ ...state.cluster }), { getTypes }, null, { withRef: true })
|
||||
@ConfigProvider.config
|
||||
class AddConfigDialog extends React.Component {
|
||||
static displayName = 'AddConfigDialog'
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
visible: false,
|
||||
clusterName: '',
|
||||
clusterType: '',
|
||||
connectKeyList: []
|
||||
}
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
clusterName: '',
|
||||
clusterType: '',
|
||||
namespace: '',
|
||||
password: '',
|
||||
userName: '',
|
||||
connectKeyList: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getTypes()
|
||||
this.props.getTypes();
|
||||
}
|
||||
|
||||
save() {
|
||||
const {clusterName, clusterType, connectKeyList} = this.state
|
||||
add({clusterName, clusterType, connectKeyList})
|
||||
.then(() => {
|
||||
this.props.turnPage(1)
|
||||
this.close()
|
||||
})
|
||||
.catch(() => this.close())
|
||||
const { clusterName, namespace, userName, password, clusterType, connectKeyList } = this.state;
|
||||
add({ clusterName, namespace, userName, password, clusterType, connectKeyList })
|
||||
.then(() => {
|
||||
this.props.turnPage(1);
|
||||
this.close();
|
||||
})
|
||||
.catch(() => this.close());
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({visible: false})
|
||||
this.setState({
|
||||
visible: false,
|
||||
clusterType: '',
|
||||
});
|
||||
}
|
||||
|
||||
open = () => this.setState({visible: true})
|
||||
open = () => this.setState({ visible: true })
|
||||
|
||||
render() {
|
||||
const {types = [], locale = {}} = this.props
|
||||
return (
|
||||
<Dialog
|
||||
className="dialog-form"
|
||||
title={locale.title}
|
||||
visible={this.state.visible}
|
||||
onOk={() => this.save()}
|
||||
onCancel={() => this.close()}
|
||||
onClose={() => this.close()}>
|
||||
<Form>
|
||||
<FormItem label={`${locale.clusterName}:`}>
|
||||
<Input
|
||||
onChange={clusterName => this.setState({clusterName})}
|
||||
placeholder={locale.clusterNamePlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.clusterType}:`}>
|
||||
<Select
|
||||
onChange={clusterType => this.setState({clusterType})}
|
||||
>
|
||||
{
|
||||
const { types = [], locale = {} } = this.props;
|
||||
return (
|
||||
<Dialog
|
||||
className="dialog-form"
|
||||
title={locale.title}
|
||||
visible={this.state.visible}
|
||||
onOk={() => this.save()}
|
||||
onCancel={() => this.close()}
|
||||
onClose={() => this.close()}
|
||||
>
|
||||
<Form>
|
||||
<FormItem label={`${locale.clusterName}:`}>
|
||||
<Input
|
||||
onChange={clusterName => this.setState({ clusterName })}
|
||||
placeholder={locale.clusterNamePlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.clusterType}:`}>
|
||||
<Select
|
||||
onChange={clusterType => this.setState({ clusterType })}
|
||||
>
|
||||
{
|
||||
types.map(type => (
|
||||
<Option value={type} key={type}>{type}</Option>
|
||||
<Option value={type} key={type}>{type}</Option>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.connectKeyList}:`}>
|
||||
<Input.TextArea
|
||||
onChange={connectKeyListStr => {
|
||||
this.setState({connectKeyList: connectKeyListStr.split('\n')})
|
||||
}}
|
||||
placeholder={locale.connectKeyListPlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
)
|
||||
</Select>
|
||||
</FormItem>
|
||||
{
|
||||
this.state.clusterType === 'NACOS' && (
|
||||
<>
|
||||
<FormItem
|
||||
label={`${locale.namespace}:`}
|
||||
>
|
||||
<Input
|
||||
placeholder={locale.namespacePlaceholder}
|
||||
onChange={ns => this.setState({ namespace: ns })}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={`${locale.username}:`}
|
||||
>
|
||||
<Input
|
||||
placeholder={locale.usernamePlaceholder}
|
||||
onChange={un => this.setState({ userName: un })}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label={`${locale.password}:`}
|
||||
>
|
||||
<Input.Password
|
||||
placeholder={locale.passwordPlaceholder}
|
||||
onChange={pw => this.setState({ password: pw })}
|
||||
/>
|
||||
</FormItem>
|
||||
</>)
|
||||
}
|
||||
<FormItem label={`${locale.connectKeyList}:`}>
|
||||
<Input.TextArea
|
||||
onChange={(connectKeyListStr) => {
|
||||
this.setState({ connectKeyList: connectKeyListStr.split('\n') });
|
||||
}}
|
||||
placeholder={locale.connectKeyListPlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddConfigDialog
|
||||
export default AddConfigDialog;
|
||||
|
|
|
@ -1,124 +1,130 @@
|
|||
import React from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {Table, Pagination, Form, Input, Button, Dialog, Message, ConfigProvider} from '@alifd/next'
|
||||
import FuncHead from '../../components/FuncHead'
|
||||
import AddConfigDialog from './AddConfigDialog'
|
||||
import {deleteCluster, list} from '../../reducers/cluster'
|
||||
import './index.scss'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Table, Pagination, Form, Input, Button, Dialog, Message, ConfigProvider } from '@alifd/next';
|
||||
import FuncHead from '../../components/FuncHead';
|
||||
import AddConfigDialog from './AddConfigDialog';
|
||||
import { deleteCluster, list } from '../../reducers/cluster';
|
||||
import './index.scss';
|
||||
|
||||
const FormItem = Form.Item
|
||||
const FormItem = Form.Item;
|
||||
|
||||
@connect(state => ({...state.cluster}), {list})
|
||||
@connect(state => ({ ...state.cluster }), { list })
|
||||
@ConfigProvider.config
|
||||
class ClusterConfig extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.addDialog = React.createRef()
|
||||
this.state = {
|
||||
pageNum: 1,
|
||||
loading: true,
|
||||
search: {
|
||||
clusterName: ''
|
||||
}
|
||||
static displayName = 'ClusterConfig'
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.addDialog = React.createRef();
|
||||
this.state = {
|
||||
pageNum: 1,
|
||||
loading: true,
|
||||
search: {
|
||||
clusterName: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { pageNum } = this.state;
|
||||
this.props.list({ pageNum }).then(() => this.loadComplete());
|
||||
}
|
||||
|
||||
loadComplete() {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
deleteServiceSync(record) {
|
||||
const { clusterId } = record;
|
||||
const { locale = {}, clusterModels = [] } = this.props;
|
||||
Dialog.confirm({
|
||||
title: locale.confirm,
|
||||
content: locale.confirmMsg,
|
||||
onOk: () => deleteCluster({ clusterId }).then(() => {
|
||||
let pageNum = 1;
|
||||
if (this.state.pageNum > 1) {
|
||||
if (clusterModels.length === 1) {
|
||||
pageNum = this.state.pageNum - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.turnPage(pageNum);
|
||||
Message.success(locale.successMsg);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {pageNum} = this.state
|
||||
const {list} = this.props
|
||||
list({pageNum}).then(() => this.loadComplete())
|
||||
}
|
||||
turnPage(pageNum) {
|
||||
const { search } = this.state;
|
||||
this.setState({
|
||||
pageNum,
|
||||
loading: true,
|
||||
}, () => this.props.list({ pageNum, ...search }).then(() => this.loadComplete()));
|
||||
}
|
||||
|
||||
loadComplete() {
|
||||
this.setState({loading: false})
|
||||
}
|
||||
onChangeSearchForm(obj) {
|
||||
const { search } = this.state;
|
||||
this.setState({ search: Object.assign({}, search, obj) });
|
||||
}
|
||||
|
||||
deleteServiceSync(record) {
|
||||
const {clusterId} = record
|
||||
const {locale = {}, clusterModels = []} = this.props
|
||||
Dialog.confirm({
|
||||
title: locale.confirm,
|
||||
content: locale.confirmMsg,
|
||||
onOk: () => deleteCluster({clusterId}).then(() => {
|
||||
let pageNum = 1
|
||||
if (this.state.pageNum > 1) {
|
||||
if (clusterModels.length === 1) {
|
||||
pageNum = this.state.pageNum - 1
|
||||
}
|
||||
}
|
||||
this.turnPage(pageNum)
|
||||
Message.success(locale.successMsg)
|
||||
})
|
||||
})
|
||||
}
|
||||
openAddDialog() {
|
||||
this.addDialog.current.getWrappedInstance().getInstance().open();
|
||||
}
|
||||
|
||||
turnPage(pageNum) {
|
||||
const {search} = this.state
|
||||
const {list} = this.props
|
||||
this.setState({pageNum, loading: true}, () => list({pageNum, ...search}).then(() => this.loadComplete()))
|
||||
}
|
||||
render() {
|
||||
const { loading, pageNum, search } = this.state;
|
||||
const { types = [], clusterModels = [], locale = {}, totalSize = 0, totalPage = 0 } = this.props;
|
||||
|
||||
onChangeSearchForm(obj) {
|
||||
const {search} = this.state
|
||||
this.setState({search: Object.assign({}, search, obj)})
|
||||
}
|
||||
|
||||
openAddDialog() {
|
||||
this.addDialog.current.getWrappedInstance().getInstance().open()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {loading, pageNum, search} = this.state
|
||||
const {types = [], clusterModels = [], locale = {}, totalSize = 0, totalPage = 0} = this.props
|
||||
|
||||
return (
|
||||
<div className="cluster-config">
|
||||
<FuncHead title={locale.title}/>
|
||||
<Form inline className="search-form">
|
||||
<FormItem label={`${locale.clusterName}:`}>
|
||||
<Input
|
||||
style={{width: 198}}
|
||||
value={search.clusterName}
|
||||
placeholder={locale.clusterNamePlaceholder}
|
||||
onChange={clusterName => this.onChangeSearchForm({clusterName})}
|
||||
/>
|
||||
</FormItem>
|
||||
<Button type="primary" onClick={() => this.turnPage(1)}>{locale.search}</Button>
|
||||
<Button
|
||||
type="normal"
|
||||
className="add-btn"
|
||||
onClick={() => this.openAddDialog()}
|
||||
>{locale.addCluster}</Button>
|
||||
</Form>
|
||||
<Table dataSource={clusterModels} loading={loading}>
|
||||
<Table.Column title={locale.clusterName} dataIndex="clusterName"/>
|
||||
<Table.Column title={locale.clusterType} dataIndex="clusterType"/>
|
||||
<Table.Column title={locale.connectKeyList} dataIndex="connectKeyList"/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => (
|
||||
<Button
|
||||
text
|
||||
type="primary"
|
||||
onClick={() => this.deleteServiceSync(record)}
|
||||
>{locale.deleteBtn}</Button>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
{
|
||||
totalPage > 1
|
||||
? <Pagination
|
||||
onChange={pageNum => this.turnPage(pageNum)}
|
||||
current={pageNum}
|
||||
total={totalSize}
|
||||
className="list-pagination"
|
||||
/>
|
||||
: null
|
||||
}
|
||||
<AddConfigDialog ref={this.addDialog} types={types} turnPage={pn => this.turnPage(pn)}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="cluster-config">
|
||||
<FuncHead title={locale.title} />
|
||||
<Form inline className="search-form">
|
||||
<FormItem label={`${locale.clusterName}:`}>
|
||||
<Input
|
||||
style={{ width: 198 }}
|
||||
value={search.clusterName}
|
||||
placeholder={locale.clusterNamePlaceholder}
|
||||
onChange={clusterName => this.onChangeSearchForm({ clusterName })}
|
||||
/>
|
||||
</FormItem>
|
||||
<Button type="primary" onClick={() => this.turnPage(1)}>{locale.search}</Button>
|
||||
<Button
|
||||
type="normal"
|
||||
className="add-btn"
|
||||
onClick={() => this.openAddDialog()}
|
||||
>{locale.addCluster}
|
||||
</Button>
|
||||
</Form>
|
||||
<Table dataSource={clusterModels} loading={loading}>
|
||||
<Table.Column title={locale.clusterName} dataIndex="clusterName" />
|
||||
<Table.Column title={locale.clusterType} dataIndex="clusterType" />
|
||||
<Table.Column title={locale.connectKeyList} dataIndex="connectKeyList" />
|
||||
<Table.Column title={locale.namespace} dataIndex="namespace" />
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => (
|
||||
<Button
|
||||
text
|
||||
type="primary"
|
||||
onClick={() => this.deleteServiceSync(record)}
|
||||
>{locale.deleteBtn}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
{
|
||||
totalPage > 1 && (
|
||||
<Pagination
|
||||
onChange={currentPageNum => this.turnPage(currentPageNum)}
|
||||
current={pageNum}
|
||||
total={totalSize}
|
||||
className="list-pagination"
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AddConfigDialog ref={this.addDialog} types={types} turnPage={pn => this.turnPage(pn)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ClusterConfig
|
||||
export default ClusterConfig;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import ClusterConfig from './ClusterConfig'
|
||||
import ClusterConfig from './ClusterConfig';
|
||||
|
||||
export default ClusterConfig
|
||||
export default ClusterConfig;
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import React from 'react'
|
||||
import {Grid} from '@alifd/next'
|
||||
import Header from '../../components/Header'
|
||||
import Menu from '../../components/Menu'
|
||||
import './index.scss'
|
||||
import React from 'react';
|
||||
import { Grid } from '@alifd/next';
|
||||
import Header from '../../components/Header';
|
||||
import Menu from '../../components/Menu';
|
||||
import './index.scss';
|
||||
|
||||
const {Row, Col} = Grid
|
||||
const { Row, Col } = Grid;
|
||||
|
||||
class Layout extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="containers">
|
||||
<Header/>
|
||||
<Row className="layout">
|
||||
<Col fixedSpan="9" className="nav-bar">
|
||||
<h1 className="title">Nacos-Sync 3.0</h1>
|
||||
<Menu/>
|
||||
</Col>
|
||||
<Col className="main-panel">{this.props.children}</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="containers">
|
||||
<Header />
|
||||
<Row className="layout">
|
||||
<Col fixedSpan="9" className="nav-bar">
|
||||
<h1 className="title">Nacos-Sync 0.4.8</h1>
|
||||
<Menu />
|
||||
</Col>
|
||||
<Col className="main-panel">{this.props.children}</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Layout
|
||||
export default Layout;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import Layout from './Layout'
|
||||
import Layout from './Layout';
|
||||
|
||||
export default Layout
|
||||
export default Layout;
|
||||
|
|
|
@ -1,60 +1,113 @@
|
|||
import React from 'react'
|
||||
import {Form, Input, Select, Dialog, ConfigProvider} from '@alifd/next'
|
||||
import '../../style/dialog-form.scss'
|
||||
import React from 'react';
|
||||
import { Form, Input, Select, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import '../../style/dialog-form.scss';
|
||||
import connect from 'react-redux/es/connect/connect';
|
||||
import { add } from '../../reducers/task';
|
||||
import { list } from '../../reducers/cluster';
|
||||
|
||||
const FormItem = Form.Item
|
||||
const FormItem = Form.Item;
|
||||
const { Option } = Select;
|
||||
|
||||
@connect(state => ({ ...state.cluster }), { list }, null, { withRef: true })
|
||||
@ConfigProvider.config
|
||||
class AddSyncDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
static displayName = 'AddSyncDialog'
|
||||
|
||||
save() {
|
||||
this.close()
|
||||
console.log('save...')
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
destClusterId: '',
|
||||
groupName: '',
|
||||
serviceName: '',
|
||||
sourceClusterId: '',
|
||||
version: '',
|
||||
sourceCluster: {},
|
||||
};
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({visible: false})
|
||||
}
|
||||
componentDidMount() {
|
||||
this.props.list({ pageSize: 10000000, pageNum: 1 });
|
||||
}
|
||||
|
||||
open = () => this.setState({visible: true})
|
||||
save() {
|
||||
const { destClusterId, groupName, serviceName, sourceClusterId, version } = this.state;
|
||||
add({ destClusterId, groupName, serviceName, sourceClusterId, version })
|
||||
.then(() => {
|
||||
this.props.turnPage(1);
|
||||
this.close();
|
||||
})
|
||||
.catch(() => this.close());
|
||||
}
|
||||
|
||||
render() {
|
||||
const {locale = {}} = this.props
|
||||
return (
|
||||
<Dialog
|
||||
className="dialog-form"
|
||||
title={locale.title}
|
||||
visible={this.state.visible}
|
||||
onOk={() => this.save()}
|
||||
onCancel={() => this.close()}
|
||||
onClose={() => this.close()}>
|
||||
<Form>
|
||||
<FormItem label={`${locale.serviceName}:`}>
|
||||
<Input placeholder={locale.serviceNamePlaceholder}/>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.groupName}:`}>
|
||||
<Input placeholder={locale.groupNamePlaceholder}/>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.sourceCluster}:`}>
|
||||
<Select>
|
||||
<Select.Option value="option1">CS</Select.Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.destCluster}:`}>
|
||||
<Select>
|
||||
<Select.Option value="option1">Nacos</Select.Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
close() {
|
||||
this.setState({ visible: false, sourceCluster: {} });
|
||||
}
|
||||
|
||||
onSourceClusterChange(sourceClusterId) {
|
||||
const [sourceCluster] = this.props.clusterModels.filter(({ clusterId }) => clusterId === sourceClusterId);
|
||||
this.setState({ sourceClusterId, sourceCluster });
|
||||
}
|
||||
|
||||
open = () => this.setState({ visible: true })
|
||||
|
||||
render() {
|
||||
const { sourceCluster } = this.state;
|
||||
const { locale = {}, clusterModels = [] } = this.props;
|
||||
return (
|
||||
<Dialog
|
||||
className="dialog-form"
|
||||
title={locale.title}
|
||||
visible={this.state.visible}
|
||||
onOk={() => this.save()}
|
||||
onCancel={() => this.close()}
|
||||
onClose={() => this.close()}
|
||||
>
|
||||
<Form>
|
||||
<FormItem label={`${locale.serviceName}:`}>
|
||||
<Input
|
||||
placeholder={locale.serviceNamePlaceholder}
|
||||
onChange={serviceName => this.setState({ serviceName })}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.groupName}:`}>
|
||||
<Input
|
||||
placeholder={locale.groupNamePlaceholder}
|
||||
onChange={groupName => this.setState({ groupName })}
|
||||
/>
|
||||
</FormItem>
|
||||
{
|
||||
sourceCluster.clusterType === 'ZK' && (
|
||||
<FormItem label={`${locale.version}:`}>
|
||||
<Input
|
||||
placeholder={locale.versionPlaceholder}
|
||||
onChange={version => this.setState({ version })}
|
||||
/>
|
||||
</FormItem>
|
||||
)
|
||||
}
|
||||
<FormItem label={`${locale.sourceCluster}:`}>
|
||||
<Select onChange={value => this.onSourceClusterChange(value)}>
|
||||
{
|
||||
clusterModels.map(({ clusterId, clusterName }) => (
|
||||
<Option key={clusterId} value={clusterId}>{clusterName}</Option>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.destCluster}:`}>
|
||||
<Select onChange={destClusterId => this.setState({ destClusterId })}>
|
||||
{
|
||||
clusterModels.map(({ clusterId, clusterName }) => (
|
||||
<Option key={clusterId} value={clusterId}>{clusterName}</Option>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddSyncDialog
|
||||
export default AddSyncDialog;
|
||||
|
|
|
@ -1,140 +1,187 @@
|
|||
import React from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {Table, Pagination, Form, Input, Button, Dialog, Message, ConfigProvider} from '@alifd/next'
|
||||
import FuncHead from '../../components/FuncHead'
|
||||
import AddSyncDialog from './AddSyncDialog'
|
||||
import {list, update} from '../../reducers/task'
|
||||
import './index.scss'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Table, Pagination, Form, Input, Button, Dialog, Message, ConfigProvider } from '@alifd/next';
|
||||
import FuncHead from '../../components/FuncHead';
|
||||
import AddSyncDialog from './AddSyncDialog';
|
||||
import { list, update, deleteRow } from '../../reducers/task';
|
||||
import { list as getClusterList } from '../../reducers/cluster';
|
||||
import './index.scss';
|
||||
|
||||
const FormItem = Form.Item
|
||||
const FormItem = Form.Item;
|
||||
|
||||
@connect(state => ({...state.task}), {list})
|
||||
@connect(state => ({ ...state.task, clusterModels: state.cluster.clusterModels }), { list, getClusterList })
|
||||
@ConfigProvider.config
|
||||
class ServiceSync extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.addDialog = React.createRef()
|
||||
this.state = {
|
||||
pageNum: 1,
|
||||
loading: true,
|
||||
search: {
|
||||
serviceName: ''
|
||||
}
|
||||
static displayName = 'ServiceSync'
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.addDialog = React.createRef();
|
||||
this.state = {
|
||||
pageNum: 1,
|
||||
loading: true,
|
||||
search: {
|
||||
serviceName: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { pageNum } = this.state;
|
||||
this.props.list({ pageNum }).then(() => this.loadComplete());
|
||||
this.props.getClusterList({ pageSize: 10000000, pageNum: 1 });
|
||||
}
|
||||
|
||||
loadComplete() {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
turnPage(pageNum) {
|
||||
const { search } = this.state;
|
||||
this.setState({
|
||||
pageNum,
|
||||
loading: true,
|
||||
}, () => this.props.list({ pageNum, ...search }).then(() => this.loadComplete()));
|
||||
}
|
||||
|
||||
deleteServiceSync(record) {
|
||||
const { taskId } = record;
|
||||
const { locale = {} } = this.props;
|
||||
Dialog.confirm({
|
||||
title: locale.confirm,
|
||||
content: locale.confirmMsg,
|
||||
onOk: () => deleteRow({ taskId }).then(() => {
|
||||
this.turnPage(this.state.pageNum);
|
||||
Message.success(locale.deleteSuccessMsg);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
suspendedServiceSync(record) {
|
||||
const { taskId } = record;
|
||||
const { locale = {} } = this.props;
|
||||
Dialog.confirm({
|
||||
title: locale.confirm,
|
||||
content: locale.suspendedMsg,
|
||||
onOk: () => update({ taskId, taskStatus: 'DELETE' }).then(() => {
|
||||
this.turnPage(this.state.pageNum);
|
||||
Message.success(locale.successMsg);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
resynchronize(record) {
|
||||
const { taskId } = record;
|
||||
const { locale = {} } = this.props;
|
||||
update({ taskId, taskStatus: 'SYNC' }).then(() => {
|
||||
this.turnPage(this.state.pageNum);
|
||||
Message.success(locale.syncSuccessMsg);
|
||||
});
|
||||
}
|
||||
|
||||
onChangeSearchForm(obj) {
|
||||
const { search } = this.state;
|
||||
this.setState({ search: Object.assign({}, search, obj) });
|
||||
}
|
||||
|
||||
openAddDialog() {
|
||||
this.addDialog.current.getWrappedInstance().getInstance().open();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, pageNum, search } = this.state;
|
||||
const { taskModels = [], locale = {}, totalSize = 0, totalPage = 0, clusterModels = [] } = this.props;
|
||||
const clusterMap = {};
|
||||
clusterModels.forEach(({ clusterId, clusterName }) => {
|
||||
clusterMap[clusterId] = clusterName;
|
||||
});
|
||||
return (
|
||||
<div className="service-sync">
|
||||
<FuncHead title={locale.title} />
|
||||
<Form inline className="search-form">
|
||||
<FormItem label={`${locale.serviceName}:`}>
|
||||
<Input
|
||||
style={{ width: 198 }}
|
||||
value={search.serviceName}
|
||||
placeholder={locale.serviceNamePlaceholder}
|
||||
onChange={serviceName => this.onChangeSearchForm({ serviceName })}
|
||||
/>
|
||||
</FormItem>
|
||||
<Button type="primary" onClick={() => this.turnPage(1)}>{locale.search}</Button>
|
||||
<Button
|
||||
type="normal"
|
||||
className="add-btn"
|
||||
onClick={() => this.openAddDialog()}
|
||||
>{locale.addSync}
|
||||
</Button>
|
||||
</Form>
|
||||
<Table dataSource={taskModels} loading={loading}>
|
||||
<Table.Column title={locale.serviceName} dataIndex="serviceName" />
|
||||
<Table.Column title={locale.groupName} dataIndex="groupName" />
|
||||
<Table.Column
|
||||
title={locale.sourceCluster}
|
||||
dataIndex="sourceClusterId"
|
||||
cell={key => clusterMap[key]}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.destCluster}
|
||||
dataIndex="destClusterId"
|
||||
cell={key => clusterMap[key]}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => {
|
||||
const buttonList = [];
|
||||
if (record.taskStatus === 'SYNC') {
|
||||
buttonList.push(
|
||||
<Button
|
||||
key="suspendedBtn"
|
||||
text
|
||||
type="primary"
|
||||
onClick={() => this.suspendedServiceSync(record)}
|
||||
>{locale.suspendedBtn}
|
||||
</Button>,
|
||||
);
|
||||
}
|
||||
if (record.taskStatus === 'DELETE') {
|
||||
buttonList.push(
|
||||
<Button
|
||||
key="resynchronizeBtn"
|
||||
text
|
||||
type="primary"
|
||||
onClick={() => this.resynchronize(record)}
|
||||
>{locale.resynchronizeBtn}
|
||||
</Button>,
|
||||
);
|
||||
buttonList.push(
|
||||
<Button
|
||||
key="deleteBtn"
|
||||
text
|
||||
type="primary"
|
||||
style={{ marginLeft: 18 }}
|
||||
onClick={() => this.deleteServiceSync(record)}
|
||||
>{locale.deleteBtn}
|
||||
</Button>,
|
||||
);
|
||||
}
|
||||
return buttonList;
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
{
|
||||
totalPage > 1 && (
|
||||
<Pagination
|
||||
onChange={currentPageNum => this.turnPage(currentPageNum)}
|
||||
current={pageNum}
|
||||
total={totalSize}
|
||||
className="list-pagination"
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {pageNum} = this.state
|
||||
const {list} = this.props
|
||||
list({pageNum}).then(() => this.loadComplete())
|
||||
}
|
||||
|
||||
loadComplete() {
|
||||
this.setState({loading: false})
|
||||
}
|
||||
|
||||
turnPage(pageNum) {
|
||||
const {search} = this.state
|
||||
const {list} = this.props
|
||||
this.setState({pageNum, loading: true}, () => list({pageNum, ...search}).then(() => this.loadComplete()))
|
||||
}
|
||||
|
||||
deleteServiceSync(record) {
|
||||
const {taskId} = record
|
||||
const {locale = {}} = this.props
|
||||
Dialog.confirm({
|
||||
title: locale.confirm,
|
||||
content: locale.confirmMsg,
|
||||
onOk: () => update({taskId, taskStatus: 'DELETE'}).then(() => {
|
||||
this.turnPage(this.state.pageNum)
|
||||
Message.success(locale.successMsg)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
resynchronize(record) {
|
||||
const {taskId} = record
|
||||
const {locale = {}} = this.props
|
||||
update({taskId, taskStatus: 'SYNC'}).then(() => {
|
||||
this.turnPage(this.state.pageNum)
|
||||
Message.success(locale.syncSuccessMsg)
|
||||
})
|
||||
}
|
||||
|
||||
onChangeSearchForm(obj) {
|
||||
const {search} = this.state
|
||||
this.setState({search: Object.assign({}, search, obj)})
|
||||
}
|
||||
|
||||
openAddDialog() {
|
||||
this.addDialog.current.getInstance().open()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {loading, pageNum, search} = this.state
|
||||
const {taskModels = [], locale = {}, totalSize = 0, totalPage = 0} = this.props
|
||||
return (
|
||||
<div className="service-sync">
|
||||
<FuncHead title={locale.title}/>
|
||||
<Form inline className="search-form">
|
||||
<FormItem label={`${locale.serviceName}:`}>
|
||||
<Input
|
||||
style={{width: 198}}
|
||||
value={search.serviceName}
|
||||
placeholder={locale.serviceNamePlaceholder}
|
||||
onChange={serviceName => this.onChangeSearchForm({serviceName})}
|
||||
/>
|
||||
</FormItem>
|
||||
<Button type="primary" onClick={() => this.turnPage(1)}>{locale.search}</Button>
|
||||
<Button
|
||||
type="normal"
|
||||
className="add-btn"
|
||||
onClick={() => this.openAddDialog()}
|
||||
>{locale.addSync}</Button>
|
||||
</Form>
|
||||
<Table dataSource={taskModels} loading={loading}>
|
||||
<Table.Column title={locale.serviceName} dataIndex="serviceName"/>
|
||||
<Table.Column title={locale.groupName} dataIndex="groupName"/>
|
||||
<Table.Column title={locale.sourceCluster} dataIndex="sourceClusterName"/>
|
||||
<Table.Column title={locale.destCluster} dataIndex="destClusterName"/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => {
|
||||
if (record.taskStatus === 'SYNC') {
|
||||
return (
|
||||
<Button
|
||||
text
|
||||
type="primary"
|
||||
onClick={() => this.deleteServiceSync(record)}
|
||||
>{locale.deleteBtn}</Button>
|
||||
)
|
||||
}
|
||||
if (record.taskStatus === 'DELETE') {
|
||||
return (
|
||||
<Button
|
||||
text
|
||||
type="primary"
|
||||
onClick={() => this.resynchronize(record)}
|
||||
>{locale.resynchronizeBtn}</Button>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
{
|
||||
totalPage > 1
|
||||
? <Pagination
|
||||
onChange={pageNum => this.turnPage(pageNum)}
|
||||
current={pageNum}
|
||||
total={totalSize}
|
||||
className="list-pagination"
|
||||
/>
|
||||
: null
|
||||
}
|
||||
<AddSyncDialog ref={this.addDialog}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<AddSyncDialog ref={this.addDialog} turnPage={pn => this.turnPage(pn)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ServiceSync
|
||||
export default ServiceSync;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import ServiceSync from './ServiceSync'
|
||||
import ServiceSync from './ServiceSync';
|
||||
|
||||
export default ServiceSync
|
||||
export default ServiceSync;
|
||||
|
|
|
@ -1,50 +1,52 @@
|
|||
import React from 'react'
|
||||
import {Form, Input, Dialog, ConfigProvider} from '@alifd/next'
|
||||
import '../../style/dialog-form.scss'
|
||||
import React from 'react';
|
||||
import { Form, Input, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import '../../style/dialog-form.scss';
|
||||
|
||||
const FormItem = Form.Item
|
||||
const FormItem = Form.Item;
|
||||
|
||||
@ConfigProvider.config
|
||||
class AddSysConfigDialog extends React.Component {
|
||||
static displayName = 'AddSysConfigDialog'
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
visible: false
|
||||
}
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
};
|
||||
}
|
||||
|
||||
save() {
|
||||
this.close()
|
||||
console.log('save...')
|
||||
this.close();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({visible: false})
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
|
||||
open = () => this.setState({visible: true})
|
||||
open = () => this.setState({ visible: true })
|
||||
|
||||
render() {
|
||||
const {locale = {}} = this.props
|
||||
return (
|
||||
<Dialog
|
||||
className="dialog-form"
|
||||
title={locale.title}
|
||||
visible={this.state.visible}
|
||||
onOk={() => this.save()}
|
||||
onCancel={() => this.close()}
|
||||
onClose={() => this.close()}>
|
||||
<Form>
|
||||
<FormItem label={`${locale.configName}:`}>
|
||||
<Input placeholder={locale.configNamePlaceholder}/>
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.configValue}:`}>
|
||||
<Input placeholder={locale.configValuePlaceholder}/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
)
|
||||
const { locale = {} } = this.props;
|
||||
return (
|
||||
<Dialog
|
||||
className="dialog-form"
|
||||
title={locale.title}
|
||||
visible={this.state.visible}
|
||||
onOk={() => this.save()}
|
||||
onCancel={() => this.close()}
|
||||
onClose={() => this.close()}
|
||||
>
|
||||
<Form>
|
||||
<FormItem label={`${locale.configName}:`}>
|
||||
<Input placeholder={locale.configNamePlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={`${locale.configValue}:`}>
|
||||
<Input placeholder={locale.configValuePlaceholder} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddSysConfigDialog
|
||||
export default AddSysConfigDialog;
|
||||
|
|
|
@ -1,70 +1,73 @@
|
|||
import React from 'react'
|
||||
import {Table, Pagination, Form, Input, Button, Dialog, Message, ConfigProvider} from '@alifd/next'
|
||||
import FuncHead from '../../components/FuncHead'
|
||||
import AddSysConfigDialog from './AddSysConfigDialog'
|
||||
import './index.scss'
|
||||
import React from 'react';
|
||||
import { Table, Pagination, Form, Input, Button, Dialog, Message, ConfigProvider } from '@alifd/next';
|
||||
import FuncHead from '../../components/FuncHead';
|
||||
import AddSysConfigDialog from './AddSysConfigDialog';
|
||||
import './index.scss';
|
||||
|
||||
const FormItem = Form.Item
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const dataSource = []
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
dataSource.push({
|
||||
c1: 'key1',
|
||||
c2: '1',
|
||||
})
|
||||
}
|
||||
const dataSource = [];
|
||||
for (let i = 0; i < 10; i++) dataSource.push({ c1: 'key1', c2: '1' });
|
||||
|
||||
@ConfigProvider.config
|
||||
class SystemConfig extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.addDialog = React.createRef()
|
||||
}
|
||||
static displayName = 'SystemConfig'
|
||||
|
||||
deleteServiceSync() {
|
||||
const {locale = {}} = this.props
|
||||
Dialog.confirm({
|
||||
title: locale.confirm,
|
||||
content: locale.confirmMsg,
|
||||
onOk: () => setTimeout(() => Message.success(locale.successMsg), 888),
|
||||
onCancel: () => console.log('cancel')
|
||||
})
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.addDialog = React.createRef();
|
||||
}
|
||||
|
||||
openAddDialog() {
|
||||
this.addDialog.current.getInstance().open()
|
||||
}
|
||||
deleteServiceSync() {
|
||||
const { locale = {} } = this.props;
|
||||
Dialog.confirm({
|
||||
title: locale.confirm,
|
||||
content: locale.confirmMsg,
|
||||
onOk: () => setTimeout(() => Message.success(locale.successMsg), 888),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {locale = {}} = this.props
|
||||
return (
|
||||
<div className="system-config">
|
||||
<FuncHead title={locale.title}/>
|
||||
<Form inline className="search-form">
|
||||
<FormItem label={`${locale.configName}:`}>
|
||||
<Input style={{width: 198}} placeholder={locale.configNamePlaceholder}/>
|
||||
</FormItem>
|
||||
<Button type="primary">{locale.search}</Button>
|
||||
<Button type="normal" className="add-btn"
|
||||
onClick={() => this.openAddDialog()}>{locale.addConfig}</Button>
|
||||
</Form>
|
||||
<Table dataSource={dataSource}>
|
||||
<Table.Column title={locale.configName} dataIndex="c1"/>
|
||||
<Table.Column title={locale.value} dataIndex="c2"/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => (
|
||||
<Button type="primary" text onClick={() => this.deleteServiceSync(record)}
|
||||
>{locale.deleteBtn}</Button>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
<Pagination onChange={f => f} className="list-pagination"/>
|
||||
<AddSysConfigDialog ref={this.addDialog}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
openAddDialog() {
|
||||
this.addDialog.current.getInstance().open();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale = {} } = this.props;
|
||||
return (
|
||||
<div className="system-config">
|
||||
<FuncHead title={locale.title} />
|
||||
<Form inline className="search-form">
|
||||
<FormItem label={`${locale.configName}:`}>
|
||||
<Input style={{ width: 198 }} placeholder={locale.configNamePlaceholder} />
|
||||
</FormItem>
|
||||
<Button type="primary">{locale.search}</Button>
|
||||
<Button
|
||||
type="normal"
|
||||
className="add-btn"
|
||||
onClick={() => this.openAddDialog()}
|
||||
>{locale.addConfig}
|
||||
</Button>
|
||||
</Form>
|
||||
<Table dataSource={dataSource}>
|
||||
<Table.Column title={locale.configName} dataIndex="c1" />
|
||||
<Table.Column title={locale.value} dataIndex="c2" />
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => (
|
||||
<Button
|
||||
type="primary"
|
||||
text
|
||||
onClick={() => this.deleteServiceSync(record)}
|
||||
>{locale.deleteBtn}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
<Pagination onChange={f => f} className="list-pagination" />
|
||||
<AddSysConfigDialog ref={this.addDialog} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemConfig
|
||||
export default SystemConfig;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import SystemConfig from './SystemConfig'
|
||||
import SystemConfig from './SystemConfig';
|
||||
|
||||
export default SystemConfig
|
||||
export default SystemConfig;
|
||||
|
|
|
@ -1,75 +1,74 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {createStore, combineReducers, compose, applyMiddleware} from 'redux'
|
||||
import {routerReducer} from 'react-router-redux'
|
||||
import thunk from 'redux-thunk'
|
||||
import {Provider, connect} from 'react-redux'
|
||||
import {HashRouter, Route, Switch, Redirect} from 'react-router-dom'
|
||||
import {ConfigProvider} from "@alifd/next"
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
|
||||
import { routerReducer } from 'react-router-redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider, connect } from 'react-redux';
|
||||
import { HashRouter, Route, Switch, Redirect } from 'react-router-dom';
|
||||
import { ConfigProvider } from '@alifd/next';
|
||||
|
||||
import Layout from './containers/Layout'
|
||||
import ServiceSync from './containers/ServiceSync'
|
||||
import SystemConfig from './containers/SystemConfig'
|
||||
import ClusterConfig from './containers/ClusterConfig'
|
||||
import CookieHelp from './utils/cookie'
|
||||
import {LANGUAGE_KEY} from './constants'
|
||||
import Layout from './containers/Layout';
|
||||
import ServiceSync from './containers/ServiceSync';
|
||||
// import SystemConfig from './containers/SystemConfig';
|
||||
import ClusterConfig from './containers/ClusterConfig';
|
||||
import { LANGUAGE_KEY, REDUX_DEVTOOLS } from './constants';
|
||||
|
||||
import * as reducers from './reducers'
|
||||
import {changeLanguage} from "./reducers/locale"
|
||||
import * as reducers from './reducers';
|
||||
import { changeLanguage } from './reducers/locale';
|
||||
|
||||
import './index.scss'
|
||||
import './index.scss';
|
||||
|
||||
if (!CookieHelp.getValue(LANGUAGE_KEY)) {
|
||||
CookieHelp.setValue(LANGUAGE_KEY, navigator.language === 'zh-CN' ? 'zh-CN' : 'en-US')
|
||||
if (!localStorage.getItem(LANGUAGE_KEY)) {
|
||||
localStorage.setItem(LANGUAGE_KEY, navigator.language === 'zh-CN' ? 'zh-CN' : 'en-US');
|
||||
}
|
||||
|
||||
const reducer = combineReducers({
|
||||
...reducers,
|
||||
routing: routerReducer
|
||||
})
|
||||
...reducers,
|
||||
routing: routerReducer,
|
||||
});
|
||||
|
||||
const store = createStore(
|
||||
reducer,
|
||||
compose(
|
||||
applyMiddleware(thunk),
|
||||
window.devToolsExtension ? window.devToolsExtension() : f => f
|
||||
)
|
||||
)
|
||||
reducer,
|
||||
compose(
|
||||
applyMiddleware(thunk),
|
||||
window[REDUX_DEVTOOLS] ? window[REDUX_DEVTOOLS]() : f => f,
|
||||
),
|
||||
);
|
||||
|
||||
@connect(state => ({...state.locale}), {changeLanguage})
|
||||
const MENU = [
|
||||
{ path: '/', exact: true, render: () => (<Redirect to="/serviceSync" />) },
|
||||
{ path: '/serviceSync', component: ServiceSync },
|
||||
{ path: '/clusterConfig', component: ClusterConfig },
|
||||
// { path: '/systemConfig', component: SystemConfig },
|
||||
];
|
||||
|
||||
@connect(state => ({ ...state.locale }), { changeLanguage })
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
componentDidMount() {
|
||||
const language = localStorage.getItem(LANGUAGE_KEY);
|
||||
this.props.changeLanguage(language);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const language = CookieHelp.getValue(LANGUAGE_KEY)
|
||||
this.props.changeLanguage(language)
|
||||
}
|
||||
get router() {
|
||||
return (
|
||||
<HashRouter>
|
||||
<Layout>
|
||||
<Switch>
|
||||
{MENU.map(item => (<Route {...Object.assign(item, { key: item.path })} />))}
|
||||
</Switch>
|
||||
</Layout>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
|
||||
generateRouter() {
|
||||
return (
|
||||
<HashRouter>
|
||||
<Layout>
|
||||
<Switch>
|
||||
<Route path='/' exact render={() => <Redirect to="/serviceSync"/>}/>
|
||||
<Route path="/serviceSync" component={ServiceSync}/>
|
||||
<Route path="/systemConfig" component={SystemConfig}/>
|
||||
<Route path="/clusterConfig" component={ClusterConfig}/>
|
||||
</Switch>
|
||||
</Layout>
|
||||
</HashRouter>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {locale} = this.props
|
||||
return (
|
||||
<ConfigProvider locale={locale}>
|
||||
{this.generateRouter()}
|
||||
</ConfigProvider>
|
||||
)
|
||||
}
|
||||
render() {
|
||||
const { locale } = this.props;
|
||||
return (
|
||||
<ConfigProvider locale={locale}>
|
||||
{this.router}
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<Provider store={store}><App/></Provider>, document.getElementById('root'))
|
||||
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
|
||||
|
|
|
@ -1,84 +1,94 @@
|
|||
const I18N_CONF = {
|
||||
Header: {
|
||||
home: 'HOME',
|
||||
docs: 'DOCS',
|
||||
blog: 'BLOG',
|
||||
community: 'COMMUNITY',
|
||||
languageSwitchButton: '中'
|
||||
},
|
||||
Menu: {
|
||||
serviceSync: 'Service Synchronization',
|
||||
clusterConfig: 'Cluster Configuration',
|
||||
systemConfig: 'System configuration',
|
||||
},
|
||||
ClusterConfig: {
|
||||
title: 'Cluster Configuration',
|
||||
addCluster: 'New Cluster',
|
||||
search: 'Search',
|
||||
clusterName: 'Cluster Name',
|
||||
clusterNamePlaceholder: 'Please enter the cluster name',
|
||||
clusterType: 'Cluster Type',
|
||||
connectKeyList: 'Connect Key List',
|
||||
operation: 'Operation',
|
||||
deleteBtn: 'Delete',
|
||||
confirm: 'Prompt',
|
||||
confirmMsg: 'Are you sure you want to delete this row?',
|
||||
successMsg: 'Delete the success!',
|
||||
},
|
||||
AddConfigDialog: {
|
||||
title: 'New Cluster',
|
||||
clusterName: 'Cluster Name',
|
||||
clusterNamePlaceholder: 'Please enter the cluster name',
|
||||
clusterType: 'Cluster Type',
|
||||
connectKeyList: 'Connect IP',
|
||||
connectKeyListPlaceholder: 'Please enter the ip'
|
||||
},
|
||||
ServiceSync: {
|
||||
title: 'Service Synchronization',
|
||||
serviceNamePlaceholder: 'Please enter service name',
|
||||
search: 'Search',
|
||||
addSync: 'New Sync',
|
||||
serviceName: 'Service Name',
|
||||
groupName: 'Group',
|
||||
sourceCluster: 'Source Cluster',
|
||||
destCluster: 'Dest Cluster',
|
||||
instancesCount: 'Instances Count',
|
||||
operation: 'Operation',
|
||||
deleteBtn: 'Delete',
|
||||
resynchronizeBtn: 'Resynchronize',
|
||||
confirm: 'Prompt',
|
||||
confirmMsg: 'Are you sure you want to delete this row?',
|
||||
successMsg: 'Delete the success!',
|
||||
syncSuccessMsg: 'Resync succeeded!',
|
||||
},
|
||||
AddSyncDialog: {
|
||||
title: 'New Sync',
|
||||
serviceName: 'Service Name',
|
||||
serviceNamePlaceholder: 'Please enter service name',
|
||||
groupName: 'Group Name',
|
||||
groupNamePlaceholder: 'Please enter group name',
|
||||
sourceCluster: 'Source Cluster',
|
||||
destCluster: 'Dest Cluster'
|
||||
},
|
||||
SystemConfig: {
|
||||
title: 'System configuration',
|
||||
configName: 'Config Name',
|
||||
configNamePlaceholder: 'Please enter config name',
|
||||
search: 'Search',
|
||||
addConfig: 'New SysConfig',
|
||||
value: 'Value',
|
||||
operation: 'Operation',
|
||||
deleteBtn: 'Delete',
|
||||
confirm: 'Prompt',
|
||||
confirmMsg: 'Are you sure you want to delete this row?',
|
||||
successMsg: 'Delete the success!',
|
||||
},
|
||||
AddSysConfigDialog: {
|
||||
title: 'New System configuration',
|
||||
configName: 'Config Name',
|
||||
configNamePlaceholder: 'Please enter config name',
|
||||
configValue: 'Config Value',
|
||||
configValuePlaceholder: 'Please enter config value',
|
||||
}
|
||||
}
|
||||
export default I18N_CONF
|
||||
Header: {
|
||||
home: 'HOME',
|
||||
docs: 'DOCS',
|
||||
blog: 'BLOG',
|
||||
community: 'COMMUNITY',
|
||||
languageSwitchButton: '中',
|
||||
},
|
||||
Menu: {
|
||||
serviceSync: 'Service Synchronization',
|
||||
clusterConfig: 'Cluster Configuration',
|
||||
systemConfig: 'System configuration',
|
||||
},
|
||||
ClusterConfig: {
|
||||
title: 'Cluster Configuration',
|
||||
addCluster: 'New Cluster',
|
||||
search: 'Search',
|
||||
clusterName: 'Cluster Name',
|
||||
clusterNamePlaceholder: 'Please enter the cluster name',
|
||||
clusterType: 'Cluster Type',
|
||||
connectKeyList: 'Connect Key List',
|
||||
namespace: 'Namespace',
|
||||
password: 'Password',
|
||||
username: 'Username',
|
||||
operation: 'Operation',
|
||||
deleteBtn: 'Delete',
|
||||
confirm: 'Prompt',
|
||||
confirmMsg: 'Are you sure you want to delete this row?',
|
||||
successMsg: 'Delete the success!',
|
||||
},
|
||||
AddConfigDialog: {
|
||||
title: 'New Cluster',
|
||||
clusterName: 'Cluster Name',
|
||||
clusterNamePlaceholder: 'Please enter the cluster name',
|
||||
namespace: 'Namespace',
|
||||
namespacePlaceholder: 'Please enter the namespace',
|
||||
clusterType: 'Cluster Type',
|
||||
connectKeyList: 'Connect IP',
|
||||
connectKeyListPlaceholder: 'Please enter the ip',
|
||||
},
|
||||
ServiceSync: {
|
||||
title: 'Service Synchronization',
|
||||
serviceNamePlaceholder: 'Please enter service name',
|
||||
search: 'Search',
|
||||
addSync: 'New Sync',
|
||||
serviceName: 'Service Name',
|
||||
groupName: 'Group',
|
||||
sourceCluster: 'Source Cluster',
|
||||
destCluster: 'Dest Cluster',
|
||||
instancesCount: 'Instances Count',
|
||||
operation: 'Operation',
|
||||
deleteBtn: 'Delete',
|
||||
suspendedBtn: 'Suspend',
|
||||
resynchronizeBtn: 'Resynchronize',
|
||||
confirm: 'Prompt',
|
||||
confirmMsg: 'Are you sure you want to delete this row?',
|
||||
suspendedMsg: 'Are you sure you want to pause this row?',
|
||||
successMsg: 'Suspension of success!',
|
||||
deleteSuccessMsg: 'Delete the success!',
|
||||
syncSuccessMsg: 'Resync succeeded!',
|
||||
},
|
||||
AddSyncDialog: {
|
||||
title: 'New Sync',
|
||||
serviceName: 'Service Name',
|
||||
serviceNamePlaceholder: 'Please enter service name',
|
||||
groupName: 'Group Name',
|
||||
groupNamePlaceholder: 'Please enter group name',
|
||||
sourceCluster: 'Source Cluster',
|
||||
destCluster: 'Dest Cluster',
|
||||
version: 'Version',
|
||||
versionPlaceholder: 'Please enter version',
|
||||
},
|
||||
SystemConfig: {
|
||||
title: 'System configuration',
|
||||
configName: 'Config Name',
|
||||
configNamePlaceholder: 'Please enter config name',
|
||||
search: 'Search',
|
||||
addConfig: 'New SysConfig',
|
||||
value: 'Value',
|
||||
operation: 'Operation',
|
||||
deleteBtn: 'Delete',
|
||||
confirm: 'Prompt',
|
||||
confirmMsg: 'Are you sure you want to delete this row?',
|
||||
successMsg: 'Delete the success!',
|
||||
},
|
||||
AddSysConfigDialog: {
|
||||
title: 'New System configuration',
|
||||
configName: 'Config Name',
|
||||
configNamePlaceholder: 'Please enter config name',
|
||||
configValue: 'Config Value',
|
||||
configValuePlaceholder: 'Please enter config value',
|
||||
},
|
||||
};
|
||||
export default I18N_CONF;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import enUS from './en-US'
|
||||
import zhCN from './zh-CN'
|
||||
import enUS from './en-US';
|
||||
import zhCN from './zh-CN';
|
||||
|
||||
export default {enUS, zhCN}
|
||||
export default { enUS, zhCN };
|
||||
|
|
|
@ -1,84 +1,96 @@
|
|||
const I18N_CONF = {
|
||||
Header: {
|
||||
home: '首页',
|
||||
docs: '文档',
|
||||
blog: '博客',
|
||||
community: '社区',
|
||||
languageSwitchButton: 'En'
|
||||
},
|
||||
Menu: {
|
||||
serviceSync: '服务同步',
|
||||
clusterConfig: '集群配置',
|
||||
systemConfig: '系统配置',
|
||||
},
|
||||
ClusterConfig: {
|
||||
title: '集群配置',
|
||||
addCluster: '新增集群',
|
||||
search: '搜索',
|
||||
clusterName: '集群名',
|
||||
clusterNamePlaceholder: '请输入集群名',
|
||||
clusterType: '集群类型',
|
||||
connectKeyList: '集群IP列表',
|
||||
operation: '操作',
|
||||
deleteBtn: '删除',
|
||||
confirm: '提示',
|
||||
confirmMsg: '确定要删除这行数据吗?',
|
||||
successMsg: '删除成功!',
|
||||
},
|
||||
AddConfigDialog: {
|
||||
title: '新增集群',
|
||||
clusterName: '集群名',
|
||||
clusterNamePlaceholder: '请输入集群名',
|
||||
clusterType: '集群类型',
|
||||
connectKeyList: '集群IP列表',
|
||||
connectKeyListPlaceholder: '请输入集群IP'
|
||||
},
|
||||
ServiceSync: {
|
||||
title: '同步服务',
|
||||
serviceNamePlaceholder: '请输入服务名',
|
||||
search: '搜索',
|
||||
addSync: '新增同步',
|
||||
serviceName: '服务名',
|
||||
groupName: '分组',
|
||||
sourceCluster: '源集群',
|
||||
destCluster: '目标集群',
|
||||
instancesCount: '实例数',
|
||||
operation: '操作',
|
||||
deleteBtn: '删除',
|
||||
resynchronizeBtn: '重新同步',
|
||||
confirm: '提示',
|
||||
confirmMsg: '确定要删除这行数据吗?',
|
||||
successMsg: '删除成功!',
|
||||
syncSuccessMsg: '重新同步成功!',
|
||||
},
|
||||
AddSyncDialog: {
|
||||
title: '新增同步',
|
||||
serviceName: '服务名',
|
||||
serviceNamePlaceholder: '请输入服务名',
|
||||
groupName: '分组名',
|
||||
groupNamePlaceholder: '请输入分组名',
|
||||
sourceCluster: '源集群',
|
||||
destCluster: '目标集群'
|
||||
},
|
||||
SystemConfig: {
|
||||
title: '系统配置',
|
||||
configName: '配置名',
|
||||
configNamePlaceholder: '请输入配置名',
|
||||
search: '搜索',
|
||||
addConfig: '新增系统配置',
|
||||
value: '值',
|
||||
operation: '操作',
|
||||
deleteBtn: '删除',
|
||||
confirm: '提示',
|
||||
confirmMsg: '确定要删除这行数据吗?',
|
||||
successMsg: '删除成功!'
|
||||
},
|
||||
AddSysConfigDialog: {
|
||||
title: '新增系统配置',
|
||||
configName: '配置名',
|
||||
configNamePlaceholder: '请输入配置名',
|
||||
configValue: '配置值',
|
||||
configValuePlaceholder: '请输入配置值',
|
||||
}
|
||||
}
|
||||
export default I18N_CONF
|
||||
Header: {
|
||||
home: '首页',
|
||||
docs: '文档',
|
||||
blog: '博客',
|
||||
community: '社区',
|
||||
languageSwitchButton: 'En',
|
||||
},
|
||||
Menu: {
|
||||
serviceSync: '服务同步',
|
||||
clusterConfig: '集群配置',
|
||||
systemConfig: '系统配置',
|
||||
},
|
||||
ClusterConfig: {
|
||||
title: '集群配置',
|
||||
addCluster: '新增集群',
|
||||
search: '搜索',
|
||||
clusterName: '集群名',
|
||||
clusterNamePlaceholder: '请输入集群名',
|
||||
clusterType: '集群类型',
|
||||
connectKeyList: '集群IP列表',
|
||||
namespace: '命名空间',
|
||||
operation: '操作',
|
||||
deleteBtn: '删除',
|
||||
confirm: '提示',
|
||||
confirmMsg: '确定要删除这行数据吗?',
|
||||
successMsg: '删除成功!',
|
||||
},
|
||||
AddConfigDialog: {
|
||||
title: '新增集群',
|
||||
clusterName: '集群名',
|
||||
clusterNamePlaceholder: '请输入集群名',
|
||||
namespace: '命名空间',
|
||||
namespacePlaceholder: '请输入命名空间',
|
||||
password: '密码',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
username: '用户名',
|
||||
usernamePlaceholder: '请输入用户名',
|
||||
clusterType: '集群类型',
|
||||
connectKeyList: '集群IP列表',
|
||||
connectKeyListPlaceholder: '请输入集群IP',
|
||||
},
|
||||
ServiceSync: {
|
||||
title: '同步服务',
|
||||
serviceNamePlaceholder: '请输入服务名',
|
||||
search: '搜索',
|
||||
addSync: '新增同步',
|
||||
serviceName: '服务名',
|
||||
groupName: '分组',
|
||||
sourceCluster: '源集群',
|
||||
destCluster: '目标集群',
|
||||
instancesCount: '实例数',
|
||||
operation: '操作',
|
||||
deleteBtn: '删除',
|
||||
suspendedBtn: '暂停',
|
||||
resynchronizeBtn: '重新同步',
|
||||
confirm: '提示',
|
||||
confirmMsg: '确定要删除这行数据吗?',
|
||||
suspendedMsg: '确定要暂停这行数据吗?',
|
||||
successMsg: '暂停成功!',
|
||||
deleteSuccessMsg: '删除成功!',
|
||||
syncSuccessMsg: '重新同步成功!',
|
||||
},
|
||||
AddSyncDialog: {
|
||||
title: '新增同步',
|
||||
serviceName: '服务名',
|
||||
serviceNamePlaceholder: '请输入服务名',
|
||||
groupName: '分组名',
|
||||
groupNamePlaceholder: '请输入分组名',
|
||||
sourceCluster: '源集群',
|
||||
destCluster: '目标集群',
|
||||
version: '版本',
|
||||
versionPlaceholder: '请输入版本',
|
||||
},
|
||||
SystemConfig: {
|
||||
title: '系统配置',
|
||||
configName: '配置名',
|
||||
configNamePlaceholder: '请输入配置名',
|
||||
search: '搜索',
|
||||
addConfig: '新增系统配置',
|
||||
value: '值',
|
||||
operation: '操作',
|
||||
deleteBtn: '删除',
|
||||
confirm: '提示',
|
||||
confirmMsg: '确定要删除这行数据吗?',
|
||||
successMsg: '删除成功!',
|
||||
},
|
||||
AddSysConfigDialog: {
|
||||
title: '新增系统配置',
|
||||
configName: '配置名',
|
||||
configNamePlaceholder: '请输入配置名',
|
||||
configValue: '配置值',
|
||||
configValuePlaceholder: '请输入配置值',
|
||||
},
|
||||
};
|
||||
export default I18N_CONF;
|
||||
|
|
|
@ -1,52 +1,50 @@
|
|||
import request from '../utils/request'
|
||||
import {CLUSTER_LIST, GET_CLUSTER_BY_ID, CLUSTER_TYPES, PAGE_SIZE} from '../constants'
|
||||
import request from '../utils/request';
|
||||
import { CLUSTER_LIST, GET_CLUSTER_BY_ID, CLUSTER_TYPES, PAGE_SIZE } from '../constants';
|
||||
|
||||
const PATH = '/nacossync/v1/cluster/'
|
||||
const PATH = '/v1/cluster/';
|
||||
const initialState = {
|
||||
totalPage: 0,
|
||||
totalSize: 0,
|
||||
clusterModel: {},
|
||||
clusterModels: [],
|
||||
types: []
|
||||
}
|
||||
totalPage: 0,
|
||||
totalSize: 0,
|
||||
clusterModel: {},
|
||||
clusterModels: [],
|
||||
types: [],
|
||||
};
|
||||
|
||||
const add = data => request.post(`${PATH}add`, data)
|
||||
const add = data => request.post(`${PATH}add`, data);
|
||||
|
||||
const deleteCluster = data => request.delete(`${PATH}delete`, {data})
|
||||
const deleteCluster = params => request.delete(`${PATH}delete`, { params });
|
||||
|
||||
const list = params => dispatch => request.get(`${PATH}list`, {
|
||||
params: {
|
||||
...params,
|
||||
pageSize: PAGE_SIZE
|
||||
}
|
||||
params: {
|
||||
pageSize: PAGE_SIZE,
|
||||
...params,
|
||||
},
|
||||
}).then(data => dispatch({
|
||||
type: CLUSTER_LIST,
|
||||
data
|
||||
}))
|
||||
type: CLUSTER_LIST,
|
||||
data,
|
||||
}));
|
||||
|
||||
const detail = params => dispatch => request.get(`${PATH}detail`, {params}).then(({clusterModel}) => dispatch({
|
||||
type: GET_CLUSTER_BY_ID,
|
||||
data: clusterModel
|
||||
})
|
||||
)
|
||||
const detail = params => dispatch => request.get(`${PATH}detail`, { params }).then(({ clusterModel }) => dispatch({
|
||||
type: GET_CLUSTER_BY_ID,
|
||||
data: clusterModel,
|
||||
}));
|
||||
|
||||
const getTypes = () => dispatch => request.get(`${PATH}types`).then(({types}) => dispatch({
|
||||
type: CLUSTER_TYPES,
|
||||
data: types
|
||||
})
|
||||
)
|
||||
const getTypes = () => dispatch => request.get(`${PATH}types`).then(({ types }) => dispatch({
|
||||
type: CLUSTER_TYPES,
|
||||
data: types,
|
||||
}));
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case CLUSTER_LIST:
|
||||
return {...state, ...action.data}
|
||||
case GET_CLUSTER_BY_ID:
|
||||
return {...state, clusterModel: action.data}
|
||||
case CLUSTER_TYPES:
|
||||
return {...state, types: action.data}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
switch (action.type) {
|
||||
case CLUSTER_LIST:
|
||||
return { ...state, ...action.data };
|
||||
case GET_CLUSTER_BY_ID:
|
||||
return { ...state, clusterModel: action.data };
|
||||
case CLUSTER_TYPES:
|
||||
return { ...state, types: action.data };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export {list, add, deleteCluster, detail, getTypes}
|
||||
export { list, add, deleteCluster, detail, getTypes };
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import locale from './locale'
|
||||
import cluster from './cluster'
|
||||
import task from './task'
|
||||
import locale from './locale';
|
||||
import cluster from './cluster';
|
||||
import task from './task';
|
||||
|
||||
export {locale, cluster, task}
|
||||
export { locale, cluster, task };
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
import fusionEnUS from '@alifd/next/lib/locale/en-us'
|
||||
import fusionZhCN from '@alifd/next/lib/locale/zh-cn'
|
||||
import I18N from '../locales'
|
||||
import {LANGUAGE_KEY, LANGUAGE_SWITCH} from "../constants";
|
||||
import CookieHelp from "../utils/cookie";
|
||||
import fusionEnUS from '@alifd/next/lib/locale/en-us';
|
||||
import fusionZhCN from '@alifd/next/lib/locale/zh-cn';
|
||||
import I18N from '../locales';
|
||||
import { LANGUAGE_KEY, LANGUAGE_SWITCH } from '../constants';
|
||||
|
||||
const enUS = Object.assign({}, fusionEnUS, I18N.enUS)
|
||||
const zhCN = Object.assign({}, fusionZhCN, I18N.zhCN)
|
||||
const enUS = Object.assign({}, fusionEnUS, I18N.enUS);
|
||||
const zhCN = Object.assign({}, fusionZhCN, I18N.zhCN);
|
||||
|
||||
const initialState = {
|
||||
language: 'en-US',
|
||||
locale: enUS
|
||||
}
|
||||
language: 'en-US',
|
||||
locale: enUS,
|
||||
};
|
||||
|
||||
const changeLanguage = language => dispatch => {
|
||||
language = language === 'zh-CN' ? 'zh-CN' : 'en-US'
|
||||
CookieHelp.setValue(LANGUAGE_KEY, language)
|
||||
dispatch({type: LANGUAGE_SWITCH, language, locale: language === 'zh-CN' ? zhCN : enUS})
|
||||
}
|
||||
const changeLanguage = language => (dispatch) => {
|
||||
const newLan = language === 'zh-CN' ? 'zh-CN' : 'en-US';
|
||||
localStorage.setItem(LANGUAGE_KEY, newLan);
|
||||
dispatch({ type: LANGUAGE_SWITCH, language: newLan, locale: newLan === 'zh-CN' ? zhCN : enUS });
|
||||
};
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case LANGUAGE_SWITCH:
|
||||
return {...state, ...action}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
switch (action.type) {
|
||||
case LANGUAGE_SWITCH:
|
||||
return { ...state, ...action };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export {changeLanguage}
|
||||
export { changeLanguage };
|
||||
|
|
|
@ -1,44 +1,45 @@
|
|||
import request from '../utils/request'
|
||||
import {TASK_LIST, GET_TASK_BY_ID, PAGE_SIZE} from '../constants'
|
||||
import request from '../utils/request';
|
||||
import { TASK_LIST, GET_TASK_BY_ID, PAGE_SIZE } from '../constants';
|
||||
|
||||
const PATH = '/nacossync/v1/task/'
|
||||
const PATH = '/v1/task/';
|
||||
const initialState = {
|
||||
totalPage: 0,
|
||||
totalSize: 0,
|
||||
taskModel: {},
|
||||
taskModels: [],
|
||||
types: []
|
||||
}
|
||||
totalPage: 0,
|
||||
totalSize: 0,
|
||||
taskModel: {},
|
||||
taskModels: [],
|
||||
types: [],
|
||||
};
|
||||
|
||||
const add = data => request.post(`${PATH}add`, data)
|
||||
const add = data => request.post(`${PATH}add`, data);
|
||||
|
||||
const update = data => request.post(`${PATH}update`, data)
|
||||
const update = data => request.post(`${PATH}update`, data);
|
||||
|
||||
const deleteRow = params => request.delete(`${PATH}delete`, { params });
|
||||
|
||||
const list = params => dispatch => request.get(`${PATH}list`, {
|
||||
params: {
|
||||
...params,
|
||||
pageSize: PAGE_SIZE
|
||||
}
|
||||
params: {
|
||||
pageSize: PAGE_SIZE,
|
||||
...params,
|
||||
},
|
||||
}).then(data => dispatch({
|
||||
type: TASK_LIST,
|
||||
data
|
||||
}))
|
||||
type: TASK_LIST,
|
||||
data,
|
||||
}));
|
||||
|
||||
const detail = params => dispatch => request.get(`${PATH}detail`, {params}).then(({taskModel}) => dispatch({
|
||||
type: GET_TASK_BY_ID,
|
||||
data: taskModel
|
||||
})
|
||||
)
|
||||
const detail = params => dispatch => request.get(`${PATH}detail`, { params }).then(({ taskModel }) => dispatch({
|
||||
type: GET_TASK_BY_ID,
|
||||
data: taskModel,
|
||||
}));
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case TASK_LIST:
|
||||
return {...state, ...action.data}
|
||||
case GET_TASK_BY_ID:
|
||||
return {...state, taskModel: action.data}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
switch (action.type) {
|
||||
case TASK_LIST:
|
||||
return { ...state, ...action.data };
|
||||
case GET_TASK_BY_ID:
|
||||
return { ...state, taskModel: action.data };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export {list, add, update, detail}
|
||||
export { list, add, update, deleteRow, detail };
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import axios from 'axios'
|
||||
import {Message} from '@alifd/next'
|
||||
import {SUCCESS_RESULT_CODE} from '../constants'
|
||||
import axios from 'axios';
|
||||
import { Message } from '@alifd/next';
|
||||
import { SUCCESS_RESULT_CODE } from '../constants';
|
||||
|
||||
const API_GENERAL_ERROR_MESSAGE = 'Request error, please try again later!'
|
||||
const API_GENERAL_ERROR_MESSAGE = 'Request error, please try again later!';
|
||||
|
||||
const request = () => {
|
||||
const instance = axios.create()
|
||||
const instance = axios.create();
|
||||
|
||||
instance.interceptors.response.use(response => {
|
||||
const {success, resultCode, resultMessage = API_GENERAL_ERROR_MESSAGE} = response.data
|
||||
if (!success && resultCode !== SUCCESS_RESULT_CODE) {
|
||||
Message.error(resultMessage)
|
||||
return Promise.reject(new Error(resultMessage))
|
||||
}
|
||||
return response.data
|
||||
}, error => {
|
||||
if (error.response) {
|
||||
const {status} = error.response
|
||||
Message.error(`HTTP ERROR: ${status}`)
|
||||
} else {
|
||||
Message.error(API_GENERAL_ERROR_MESSAGE)
|
||||
}
|
||||
return Promise.reject(error)
|
||||
})
|
||||
instance.interceptors.response.use((response) => {
|
||||
const { success, resultCode, resultMessage = API_GENERAL_ERROR_MESSAGE } = response.data;
|
||||
if (!success && resultCode !== SUCCESS_RESULT_CODE) {
|
||||
Message.error(resultMessage);
|
||||
return Promise.reject(new Error(resultMessage));
|
||||
}
|
||||
return response.data;
|
||||
}, (error) => {
|
||||
if (error.response) {
|
||||
const { status } = error.response;
|
||||
Message.error(`HTTP ERROR: ${status}`);
|
||||
} else {
|
||||
Message.error(API_GENERAL_ERROR_MESSAGE);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
return instance
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
export default request()
|
||||
export default request();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -5,10 +5,10 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Skywalker</title>
|
||||
<title>Nacos-Sync</title>
|
||||
<link rel="shortcut icon" href="//www.aliyun.com/favicon.ico" type="image/x-icon">
|
||||
<link href="./css/main.8760bad6.css" rel="stylesheet"></head>
|
||||
<link href="./css/main.e2917886.css" rel="stylesheet"></head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="text/javascript" src="./js/main.ddff04aa.js"></script></body>
|
||||
<script type="text/javascript" src="./js/main.b73436b5.js"></script></body>
|
||||
</html>
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,334 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (properties) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
------
|
||||
This product has a bundle logback, which is available under the EPL v1.0 License.
|
||||
The source code of logback can be found at https://github.com/qos-ch/logback.
|
||||
|
||||
Logback LICENSE
|
||||
---------------
|
||||
|
||||
Logback: the reliable, generic, fast and flexible logging framework.
|
||||
Copyright (C) 1999-2015, QOS.ch. All rights reserved.
|
||||
|
||||
This program and the accompanying materials are dual-licensed under
|
||||
either the terms of the Eclipse Public License v1.0 as published by
|
||||
the Eclipse Foundation
|
||||
|
||||
or (per the licensee's choosing)
|
||||
|
||||
under the terms of the GNU Lesser General Public License version 2.1
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
------
|
||||
This product has a bundle slf4j, which is available under the MIT License.
|
||||
The source code of slf4j can be found at https://github.com/qos-ch/slf4j.
|
||||
|
||||
Copyright (c) 2004-2017 QOS.ch
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
------
|
||||
This product has a bundle fastjson, which is available under the ASL2 License.
|
||||
The source code of fastjson can be found at https://github.com/alibaba/fastjson.
|
||||
|
||||
Copyright 1999-2016 Alibaba Group Holding Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
------
|
||||
This product has a bundle javassist, which is available under the ASL2 License.
|
||||
The source code of javassist can be found at https://github.com/jboss-javassist/javassist.
|
||||
|
||||
Copyright (C) 1999- by Shigeru Chiba, All rights reserved.
|
||||
|
||||
Javassist (JAVA programming ASSISTant) makes Java bytecode manipulation simple.
|
||||
It is a class library for editing bytecodes in Java; it enables Java programs to define a new class
|
||||
at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors,
|
||||
Javassist provides two levels of API: source level and bytecode level. If the users use the source- level API,
|
||||
they can edit a class file without knowledge of the specifications of the Java bytecode.
|
||||
The whole API is designed with only the vocabulary of the Java language.
|
||||
You can even specify inserted bytecode in the form of source text; Javassist compiles it on the fly.
|
||||
On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors.
|
||||
|
||||
This software is distributed under the Mozilla Public License Version 1.1,
|
||||
the GNU Lesser General Public License Version 2.1 or later, or the Apache License Version 2.0.
|
||||
|
||||
------
|
||||
This product has a bundle jna, which is available under the ASL2 License.
|
||||
The source code of jna can be found at https://github.com/java-native-access/jna.
|
||||
|
||||
This copy of JNA is licensed under the
|
||||
Apache (Software) License, version 2.0 ("the License").
|
||||
See the License for details about distribution rights, and the
|
||||
specific rights regarding derivate works.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
A copy is also included in the downloadable source code package
|
||||
containing JNA, in file "AL2.0", under the same directory
|
||||
as this file.
|
||||
------
|
||||
This product has a bundle guava, which is available under the ASL2 License.
|
||||
The source code of guava can be found at https://github.com/google/guava.
|
||||
|
||||
Copyright (C) 2007 The Guava authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
------
|
||||
This product has a bundle OpenMessaging, which is available under the ASL2 License.
|
||||
The source code of OpenMessaging can be found at https://github.com/openmessaging/openmessaging.
|
||||
|
||||
Copyright (C) 2017 The OpenMessaging authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
Nacos
|
||||
Copyright 2018-2019 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Alibaba MiddleWare Group.
|
||||
|
||||
------
|
||||
This product has a bundle netty:
|
||||
The Spring oot Project
|
||||
=================
|
||||
|
||||
Please visit the Netty web site for more information:
|
||||
|
||||
* http://netty.io/
|
||||
|
||||
Copyright 2014 The Netty Project
|
||||
|
||||
The Netty Project licenses this file to you under the Apache License,
|
||||
version 2.0 (the "License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at:
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Also, please refer to each LICENSE.<component>.txt file, which is located in
|
||||
the 'license' directory of the distribution file, for the license terms of the
|
||||
components that this product depends on.
|
||||
|
||||
------
|
||||
This product has a bundle commons-lang, which includes software from the Spring Framework,
|
||||
under the Apache License 2.0 (see: StringUtils.containsWhitespace())
|
|
@ -0,0 +1,46 @@
|
|||
/******************************************/
|
||||
/* DB name = nacos_sync */
|
||||
/* Table name = cluster */
|
||||
/******************************************/
|
||||
CREATE TABLE `cluster` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`cluster_id` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`cluster_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`cluster_type` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`connect_key_list` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`user_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`namespace` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`cluster_level` int default 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
/******************************************/
|
||||
/* DB name = nacos_sync */
|
||||
/* Table name = system_config */
|
||||
/******************************************/
|
||||
CREATE TABLE `system_config` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`config_desc` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`config_key` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`config_value` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
/******************************************/
|
||||
/* DB name = nacos_sync */
|
||||
/* Table name = task */
|
||||
/******************************************/
|
||||
CREATE TABLE `task` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`dest_cluster_id` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`group_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`name_space` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`operation_id` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`service_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`source_cluster_id` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`task_id` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`task_status` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`version` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`worker_ip` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`status` int default null ,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
pid=`ps ax | grep -i 'nacos-sync' |grep java | grep -v grep | awk '{print $1}'`
|
||||
if [ -z "$pid" ] ; then
|
||||
echo "no nacos-sync running."
|
||||
exit -1;
|
||||
fi
|
||||
|
||||
echo "the nacos-sync(${pid}) is running..."
|
||||
|
||||
kill ${pid}
|
||||
|
||||
echo "Send shutdown request to nacos-sync(${pid}) OK"
|
|
@ -0,0 +1 @@
|
|||
java -jar ../nacos-sync-server.jar -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m --spring.config.location=../conf/application.properties
|
|
@ -0,0 +1,95 @@
|
|||
#!/bin/bash
|
||||
|
||||
ACTION=$1
|
||||
|
||||
cygwin=false
|
||||
darwin=false
|
||||
os400=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true;;
|
||||
Darwin*) darwin=true;;
|
||||
OS400*) os400=true;;
|
||||
esac
|
||||
error_exit ()
|
||||
{
|
||||
echo "ERROR: $1 !!"
|
||||
exit 1
|
||||
}
|
||||
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java
|
||||
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
|
||||
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/opt/taobao/java
|
||||
[ ! -e "$JAVA_HOME/bin/java" ] && unset JAVA_HOME
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if $darwin; then
|
||||
|
||||
if [ -x '/usr/libexec/java_home' ] ; then
|
||||
export JAVA_HOME=`/usr/libexec/java_home`
|
||||
|
||||
elif [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home" ]; then
|
||||
export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"
|
||||
fi
|
||||
else
|
||||
JAVA_PATH=`dirname $(readlink -f $(which javac))`
|
||||
if [ "x$JAVA_PATH" != "x" ]; then
|
||||
export JAVA_HOME=`dirname $JAVA_PATH 2>/dev/null`
|
||||
fi
|
||||
fi
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better!"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
export JAVA_HOME
|
||||
export JAVA="$JAVA_HOME/bin/java"
|
||||
export BASE_DIR=`cd $(dirname $0)/..; pwd`
|
||||
|
||||
|
||||
JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
|
||||
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${BASE_DIR}/nacos-sync-java-heapdump.hprof"
|
||||
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
|
||||
JAVA_OPT="${JAVA_OPT} -Dspring.config.location=${BASE_DIR}/conf/application.properties"
|
||||
JAVA_OPT="${JAVA_OPT} -DnacosSync.home=${BASE_DIR}"
|
||||
|
||||
JAVA_MAJOR_VERSION=$($JAVA -version 2>&1 | sed -E -n 's/.* version "([0-9]*).*$/\1/p')
|
||||
if [[ "$JAVA_MAJOR_VERSION" -ge "9" ]] ; then
|
||||
JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${BASE_DIR}/logs/nacos-sync-gc.log:time,tags:filecount=10,filesize=102400"
|
||||
else
|
||||
JAVA_OPT="${JAVA_OPT} -Xloggc:${BASE_DIR}/logs/nacos-sync-gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M"
|
||||
fi
|
||||
|
||||
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/nacos-sync-server.jar"
|
||||
JAVA_OPT="${JAVA_OPT} --logging.config=${BASE_DIR}/conf/logback-spring.xml"
|
||||
|
||||
echo "JAVA_HOME:"$JAVA_HOME
|
||||
echo "BASE_DIR:"$BASE_DIR
|
||||
echo "JAVA:"$JAVA
|
||||
|
||||
|
||||
if [ ! -d "${BASE_DIR}/logs" ]; then
|
||||
mkdir "${BASE_DIR}/logs"
|
||||
fi
|
||||
|
||||
|
||||
usage(){
|
||||
echo "command error"
|
||||
}
|
||||
|
||||
start(){
|
||||
|
||||
echo "$JAVA ${JAVA_OPT}" > ${BASE_DIR}/logs/nacos-sync-start.out 2>&1 &
|
||||
nohup $JAVA ${JAVA_OPT} >> ${BASE_DIR}/logs/nacos-sync-start.out 2>&1 &
|
||||
echo "nacos-sync is starting,you can check the ${BASE_DIR}/logs/nacos-sync-start.out"
|
||||
|
||||
}
|
||||
|
||||
|
||||
case "$ACTION" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nacossync-parent</artifactId>
|
||||
<groupId>com.alibaba.nacossync</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
<artifactId>nacossync-distribution</artifactId>
|
||||
|
||||
<build>
|
||||
<finalName>nacos-sync-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<configuration>
|
||||
<!-- 打成zip包时,id不加在文件名中 -->
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<descriptors>
|
||||
<descriptor>release-nacossync.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<assembly>
|
||||
<id>bin</id>
|
||||
<includeBaseDirectory>true</includeBaseDirectory>
|
||||
<!-- file name after unzip -->
|
||||
<baseDirectory>nacos-sync</baseDirectory>
|
||||
<formats>
|
||||
<format>dir</format>
|
||||
<format>tar.gz</format>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>../nacossync-worker/src/main/resources</directory>
|
||||
<outputDirectory>conf</outputDirectory>
|
||||
<includes>
|
||||
<include>application.properties</include>
|
||||
<include>logback-spring.xml</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<includes>
|
||||
<include>conf/**</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<includes>
|
||||
<include>bin/**</include>
|
||||
</includes>
|
||||
<fileMode>0755</fileMode>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<includes>
|
||||
<include>logs/**</include>
|
||||
</includes>
|
||||
<fileMode>0755</fileMode>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
<files>
|
||||
<file>
|
||||
<source>LICENSE-BIN</source>
|
||||
<destName>LICENSE</destName>
|
||||
</file>
|
||||
<file>
|
||||
<source>NOTICE-BIN</source>
|
||||
<destName>NOTICE</destName>
|
||||
</file>
|
||||
<file>
|
||||
<source>../nacossync-worker/target/nacos-sync-server-${project.version}.jar</source>
|
||||
<outputDirectory></outputDirectory>
|
||||
<destName>nacos-sync-server.jar</destName>
|
||||
</file>
|
||||
</files>
|
||||
</assembly>
|
|
@ -17,20 +17,52 @@
|
|||
<parent>
|
||||
<artifactId>nacossync-parent</artifactId>
|
||||
<groupId>com.alibaba.nacossync</groupId>
|
||||
<version>0.1.0</version>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<artifactId>nacossync-test</artifactId>
|
||||
<dependencies>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>3.0.0.RC2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>nacossync-worker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ning</groupId>
|
||||
<artifactId>async-http-client</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<parallel>methods</parallel>
|
||||
<useUnlimitedThreads>true</useUnlimitedThreads>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
|
@ -0,0 +1,566 @@
|
|||
//package com.alibaba.nacossync;
|
||||
//
|
||||
//import java.util.HashMap;
|
||||
//import java.util.Map;
|
||||
//
|
||||
//import com.alibaba.fastjson.JSON;
|
||||
//import com.alibaba.fastjson.JSONArray;
|
||||
//import com.alibaba.fastjson.JSONObject;
|
||||
//import com.alibaba.nacossync.util.HttpClient;
|
||||
//import com.alibaba.nacossync.util.HttpClient.HttpResult;
|
||||
//
|
||||
//import org.apache.http.HttpStatus;
|
||||
//import org.junit.After;
|
||||
//import org.junit.Assert;
|
||||
//import org.junit.Before;
|
||||
//import org.junit.Test;
|
||||
//import org.junit.runner.RunWith;
|
||||
//import org.springframework.boot.web.server.LocalServerPort;
|
||||
//import org.springframework.test.context.junit4.SpringRunner;
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * Created by mingyi.xxc
|
||||
// * Date: 2019/2/23
|
||||
// * Time: 下午7:20
|
||||
// * DESC:
|
||||
// *
|
||||
// * @author mingyi.xxc
|
||||
// * @date 2019/02/23
|
||||
// */
|
||||
//@RunWith(SpringRunner.class)
|
||||
////@SpringBootTest(classes = NacosSyncMain.class, properties = {"server.servlet.context-path=/",
|
||||
//// "server.port=8081"},
|
||||
//// webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
//public class RestApiTest {
|
||||
//
|
||||
// private String baseUrl;
|
||||
//
|
||||
// @LocalServerPort
|
||||
// private int port;
|
||||
//
|
||||
// @Before
|
||||
// public void setUp() throws Exception {
|
||||
// this.baseUrl = String.format("http://localhost:%d", port);
|
||||
// }
|
||||
//
|
||||
// @After
|
||||
// public void cleanup() throws Exception {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void addCluster() {
|
||||
// String url = baseUrl + "/v1/cluster/add";
|
||||
//
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("clusterName", "CI-Nacos-Test" + System.currentTimeMillis());
|
||||
// clusterJson.put("clusterType", "NACOS");
|
||||
//
|
||||
// JSONArray jsonArray = new JSONArray();
|
||||
// jsonArray.add("11.11.11.11");
|
||||
// jsonArray.add("22.22.22.22");
|
||||
// clusterJson.put("connectKeyList", jsonArray);
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void addClusterNotHaveClusterType() {
|
||||
// String url = baseUrl + "/v1/cluster/add";
|
||||
//
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("clusterName", "CI-Nacos-Test" + System.currentTimeMillis());
|
||||
//
|
||||
// JSONArray jsonArray = new JSONArray();
|
||||
// jsonArray.add("11.11.11.11");
|
||||
// jsonArray.add("22.22.22.22");
|
||||
// clusterJson.put("connectKeyList", jsonArray);
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertFalse(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void deleteCluster() {
|
||||
// String url = baseUrl + "/v1/cluster/list";
|
||||
// String clusterId = "";
|
||||
// try {
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("pageNum", "1");
|
||||
// paramValues.put("pageSize", "50");
|
||||
//
|
||||
// HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// JSONArray clusterModels = JSON.parseObject(result.content).getJSONArray("clusterModels");
|
||||
// if (clusterModels.size() > 0) {
|
||||
// for (int i=0; i<clusterModels.size(); i++) {
|
||||
// if (clusterModels.getJSONObject(i).getString("clusterName").startsWith("CI-Nacos-Test")) {
|
||||
// clusterId = clusterModels.getJSONObject(i).getString("clusterId");
|
||||
// System.out.println("find cluster id = " + clusterId);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// url = baseUrl + "/v1/cluster/delete";
|
||||
// paramValues = new HashMap<>();
|
||||
// paramValues.put("clusterId", clusterId);
|
||||
//
|
||||
// result = HttpClient.httpDelete(url, paramValues, null, "UTF-8");
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// System.out.println(result.content);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * delete clusterId不存在
|
||||
// */
|
||||
// @Test
|
||||
// public void deleteClusterByClusterId() {
|
||||
// String clusterId = "";
|
||||
// try {
|
||||
// String url = baseUrl + "/v1/cluster/delete";
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("clusterId", clusterId);
|
||||
//
|
||||
// HttpResult result = HttpClient.httpDelete(url, paramValues, null, "UTF-8");
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// System.out.println(result.content);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void getClusterDetails() {
|
||||
// addCluster();
|
||||
//
|
||||
// String clusterId = "";
|
||||
// String url = baseUrl + "/v1/cluster/list";
|
||||
// try {
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("pageNum", "1");
|
||||
// paramValues.put("pageSize", "50");
|
||||
//
|
||||
// HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// JSONArray clusterModels = JSON.parseObject(result.content).getJSONArray("clusterModels");
|
||||
// if (clusterModels.size() > 0) {
|
||||
// for (int i=0; i<clusterModels.size(); i++) {
|
||||
// if (clusterModels.getJSONObject(i).getString("clusterName").startsWith("CI-Nacos-Test")) {
|
||||
// clusterId = clusterModels.getJSONObject(i).getString("clusterId");
|
||||
// System.out.println("find cluster id = " + clusterId);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// url = baseUrl + "/v1/cluster/detail";
|
||||
// paramValues = new HashMap<>();
|
||||
// paramValues.put("clusterId", clusterId);
|
||||
//
|
||||
// result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// Assert.assertEquals(clusterId, JSON.parseObject(result.content).getJSONObject("clusterModel").getString("clusterId"));
|
||||
// System.out.println(result.content);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void getClusterList() {
|
||||
// String url = baseUrl + "/v1/cluster/list";
|
||||
// try {
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("pageNum", "1");
|
||||
// paramValues.put("pageSize", "10");
|
||||
//
|
||||
// HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// System.out.println(result.content);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getJSONArray("clusterModels").size() >= 0);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void getClusterListByClusterName() {
|
||||
// String url = baseUrl + "/v1/cluster/list";
|
||||
// try {
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("pageNum", "1");
|
||||
// paramValues.put("pageSize", "10");
|
||||
// paramValues.put("clusterName", "-CI");
|
||||
//
|
||||
// HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getJSONArray("clusterModels").size() == 0);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void getClusterTypes() {
|
||||
// String url = baseUrl + "/v1/cluster/types";
|
||||
// try {
|
||||
// HttpResult result = HttpClient.httpGet(url, null, null);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// System.out.println(result.content);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getJSONArray("types").contains("NACOS"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void addSystemConfig() {
|
||||
// String url = baseUrl + "/v1/systemconfig/add";
|
||||
//
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("config_desc", "test");
|
||||
// clusterJson.put("config_key", "key");
|
||||
// clusterJson.put("config_value", "value");
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
//
|
||||
// System.out.println(result.content);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void deleteSystemConfig() {
|
||||
// String url = baseUrl + "/v1/systemconfig/delete";
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("config_desc", "test");
|
||||
// paramValues.put("config_key", "key");
|
||||
// paramValues.put("config_value", "value");
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpDelete(url, paramValues, null, "UTF-8");
|
||||
// //Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// //Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// System.out.println(result.content);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void getSystemConfigList() {
|
||||
// String url = baseUrl + "/v1/systemconfig/list";
|
||||
// try {
|
||||
// HttpResult result = HttpClient.httpGet(url, null, null);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// System.out.println(result.content);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void addTask() {
|
||||
// addCluster("EUREKA");
|
||||
// addCluster("NACOS");
|
||||
// String destClusterId = getClusterId("NACOS");
|
||||
// String sourceClusterId = getClusterId("EUREKA");
|
||||
//
|
||||
// String url = baseUrl + "/v1/task/add";
|
||||
//
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("destClusterId", destClusterId);
|
||||
// clusterJson.put("groupName", "eureka");
|
||||
// clusterJson.put("nameSpace", "CI-test");
|
||||
// clusterJson.put("serviceName", "CI-Nacos-Service" + System.currentTimeMillis());
|
||||
// clusterJson.put("sourceClusterId", sourceClusterId);
|
||||
// clusterJson.put("version", "1.0.0");
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void deleteTask() {
|
||||
// String url = baseUrl + "/v1/task/delete";
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("taskId", getTaskId());
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpDelete(url, paramValues, null, "UTF-8");
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// System.out.println(result.content);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void deleteTaskByTaskId() {
|
||||
// String url = baseUrl + "/v1/task/delete";
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("taskId", "");
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpDelete(url, paramValues, null, "UTF-8");
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// System.out.println(result.content);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void getTaskDetail() {
|
||||
// String taskId = getTaskId();
|
||||
// String url = baseUrl + "/v1/task/detail";
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("taskId", taskId);
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// Assert.assertEquals(taskId, JSON.parseObject(result.content).getJSONObject("taskModel").getString("taskId"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * taskId为空, 必须存在taskId
|
||||
// */
|
||||
// @Test
|
||||
// public void getTaskDetailWithTaskId() {
|
||||
// String taskId = "";
|
||||
// String url = baseUrl + "/v1/task/detail";
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("taskId", taskId);
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// System.out.print(result.content);
|
||||
// Assert.assertFalse(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void getTaskList() {
|
||||
// String url = baseUrl + "/v1/task/list";
|
||||
// try {
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("pageNum", "1");
|
||||
// paramValues.put("pageSize", "10");
|
||||
//
|
||||
// HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
//
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getJSONArray("taskModels").size() >= 0);
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void updateTask() {
|
||||
// String url = baseUrl + "/v1/task/update";
|
||||
// String taskId = getTaskId();
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("taskId", taskId);
|
||||
// clusterJson.put("taskStatus", "DELETE");
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
// System.out.println(result.content);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// JSONObject taskDetail = getTaskDetailByTaskId(taskId);
|
||||
// Assert.assertEquals("DELETE", taskDetail.getString("taskStatus"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void updateTaskWithStatusSync() {
|
||||
// String url = baseUrl + "/v1/task/update";
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("taskId", getTaskId());
|
||||
// clusterJson.put("taskStatus", "SYNC");
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
// System.out.println(result.content);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * update中必须包含taskStatus
|
||||
// */
|
||||
// @Test
|
||||
// public void updateTaskWithServiceName() {
|
||||
// String url = baseUrl + "/v1/task/update";
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("taskId", getTaskId());
|
||||
// String serviceName = "CI-Nacos-Service" + System.currentTimeMillis();
|
||||
// clusterJson.put("serviceName", serviceName);
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
// System.out.println(result.content);
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertFalse(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void addCluster(String clusterType) {
|
||||
// String url = baseUrl + "/v1/cluster/add";
|
||||
//
|
||||
// JSONObject clusterJson = new JSONObject();
|
||||
// clusterJson.put("clusterName", "CI-Nacos-Test" + System.currentTimeMillis());
|
||||
// clusterJson.put("clusterType", clusterType);
|
||||
//
|
||||
// JSONArray jsonArray = new JSONArray();
|
||||
// jsonArray.add("11.11.11.11");
|
||||
// jsonArray.add("22.22.22.22");
|
||||
// clusterJson.put("connectKeyList", jsonArray);
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpPost(url, clusterJson.toJSONString());
|
||||
// Assert.assertEquals(HttpStatus.SC_OK, result.code);
|
||||
// Assert.assertTrue(JSON.parseObject(result.content).getBoolean("success"));
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// Assert.assertTrue("error", false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private String getClusterId(String clusterType) {
|
||||
// String clusterId = "";
|
||||
// String url = baseUrl + "/v1/cluster/list";
|
||||
// try {
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("pageNum", "1");
|
||||
// paramValues.put("pageSize", "50");
|
||||
//
|
||||
// HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// JSONArray clusterModels = JSON.parseObject(result.content).getJSONArray("clusterModels");
|
||||
// if (clusterModels.size() > 0) {
|
||||
// for (int i=0; i<clusterModels.size(); i++) {
|
||||
// if (clusterModels.getJSONObject(i).getString("clusterName").startsWith("CI-Nacos-Test") && clusterModels.getJSONObject(i).getString("clusterType").equals(clusterType)) {
|
||||
// clusterId = clusterModels.getJSONObject(i).getString("clusterId");
|
||||
// System.out.println("find cluster id = " + clusterId);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// }
|
||||
// return clusterId;
|
||||
// }
|
||||
//
|
||||
// private String getTaskId() {
|
||||
// String taskId = "";
|
||||
// String url = baseUrl + "/v1/task/list";
|
||||
// try {
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("pageNum", "1");
|
||||
// paramValues.put("pageSize", "10");
|
||||
//
|
||||
// HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// JSONArray taskModels = JSON.parseObject(result.content).getJSONArray("taskModels");
|
||||
//
|
||||
// if (taskModels.size() > 0) {
|
||||
// for (int i=0; i<taskModels.size(); i++) {
|
||||
// if (taskModels.getJSONObject(i).getString("serviceName").startsWith("CI-Nacos-Service")) {
|
||||
// taskId = taskModels.getJSONObject(i).getString("taskId");
|
||||
// System.out.println("find task id = " + taskId);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// }
|
||||
// return taskId;
|
||||
// }
|
||||
//
|
||||
// private JSONObject getTaskDetailByTaskId(String taskId) {
|
||||
// String url = baseUrl + "/v1/task/detail";
|
||||
// Map<String, String> paramValues = new HashMap<>();
|
||||
// paramValues.put("taskId", taskId);
|
||||
//
|
||||
// try {
|
||||
// HttpClient.HttpResult result = HttpClient.httpGet(url, null, paramValues);
|
||||
// if (HttpStatus.SC_OK == result.code) {
|
||||
// return JSON.parseObject(result.content).getJSONObject("taskModel");
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// System.out.println(e.getMessage());
|
||||
// return null;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
//}
|
|
@ -16,7 +16,7 @@
|
|||
<parent>
|
||||
<artifactId>nacossync-parent</artifactId>
|
||||
<groupId>com.alibaba.nacossync</groupId>
|
||||
<version>0.1.0</version>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>nacossync-worker</artifactId>
|
||||
|
@ -24,19 +24,30 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- 默认使用HikariCP连接池 -->
|
||||
<dependency>
|
||||
|
@ -49,30 +60,123 @@
|
|||
</dependency>
|
||||
<!-- mysql -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.47</version>
|
||||
</dependency>
|
||||
<!-- swagger2 -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.6.1</version>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.6.1</version>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<!--nacos-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.nacos</groupId>
|
||||
<artifactId>nacos-client</artifactId>
|
||||
<version>0.5.0</version>
|
||||
</dependency>
|
||||
<!--zookeeper-->
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-client</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- eureka -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</dependency>
|
||||
<!-- consul -->
|
||||
<dependency>
|
||||
<groupId>com.ecwid.consul</groupId>
|
||||
<artifactId>consul-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>nacos-sync-server-${project.version}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>application.properties</exclude>
|
||||
<exclude>logback-spring.xml</exclude>
|
||||
</excludes>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.34</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -14,23 +14,32 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync;
|
||||
|
||||
import com.alibaba.nacossync.util.BatchTaskExecutor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SkyWalkerMain.java, v 0.1 2018-09-24 下午12:42 NacosSync Exp $$
|
||||
* @version $Id: SkyWalkerMain.java, v 0.1 2018-09-24 PM12:42 NacosSync Exp $$
|
||||
*/
|
||||
@EnableSwagger2
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(exclude = EurekaClientAutoConfiguration.class)
|
||||
public class NacosSyncMain {
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
SpringApplication.run(NacosSyncMain.class, args);
|
||||
|
||||
ConfigurableApplicationContext context = SpringApplication.run(NacosSyncMain.class, args);
|
||||
|
||||
// Register shutdown callback using Spring Boot's context lifecycle
|
||||
context.registerShutdownHook();
|
||||
context.addApplicationListener(event -> {
|
||||
if (event instanceof org.springframework.context.event.ContextClosedEvent) {
|
||||
BatchTaskExecutor.shutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,81 +14,85 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.api;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.pojo.result.*;
|
||||
import com.alibaba.nacossync.pojo.request.ClusterAddRequest;
|
||||
import com.alibaba.nacossync.pojo.request.ClusterDeleteRequest;
|
||||
import com.alibaba.nacossync.pojo.request.ClusterDetailQueryRequest;
|
||||
import com.alibaba.nacossync.pojo.request.ClusterListQueryRequest;
|
||||
import com.alibaba.nacossync.pojo.result.ClusterAddResult;
|
||||
import com.alibaba.nacossync.pojo.result.ClusterDeleteResult;
|
||||
import com.alibaba.nacossync.pojo.result.ClusterDetailQueryResult;
|
||||
import com.alibaba.nacossync.pojo.result.ClusterListQueryResult;
|
||||
import com.alibaba.nacossync.pojo.result.ClusterTypeResult;
|
||||
import com.alibaba.nacossync.template.SkyWalkerTemplate;
|
||||
import com.alibaba.nacossync.template.processor.ClusterAddProcessor;
|
||||
import com.alibaba.nacossync.template.processor.ClusterDeleteProcessor;
|
||||
import com.alibaba.nacossync.template.processor.ClusterDetailQueryProcessor;
|
||||
import com.alibaba.nacossync.template.processor.ClusterListQueryProcessor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: ClusterApi.java, v 0.1 2018-09-25 下午9:30 NacosSync Exp $$
|
||||
* @version $Id: ClusterApi.java, v 0.1 2018-09-25 PM9:30 NacosSync Exp $$
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class ClusterApi {
|
||||
|
||||
@Autowired
|
||||
private ClusterAddProcessor clusterAddProcessor;
|
||||
|
||||
@Autowired
|
||||
private ClusterDeleteProcessor clusterDeleteProcessor;
|
||||
|
||||
@Autowired
|
||||
private ClusterDetailQueryProcessor clusterDetailQueryProcessor;
|
||||
|
||||
@Autowired
|
||||
private ClusterListQueryProcessor clusterListQueryProcessor;
|
||||
|
||||
|
||||
private final ClusterAddProcessor clusterAddProcessor;
|
||||
|
||||
private final ClusterDeleteProcessor clusterDeleteProcessor;
|
||||
|
||||
private final ClusterDetailQueryProcessor clusterDetailQueryProcessor;
|
||||
|
||||
private final ClusterListQueryProcessor clusterListQueryProcessor;
|
||||
|
||||
public ClusterApi(ClusterAddProcessor clusterAddProcessor, ClusterDeleteProcessor clusterDeleteProcessor,
|
||||
ClusterDetailQueryProcessor clusterDetailQueryProcessor,
|
||||
ClusterListQueryProcessor clusterListQueryProcessor) {
|
||||
this.clusterAddProcessor = clusterAddProcessor;
|
||||
this.clusterDeleteProcessor = clusterDeleteProcessor;
|
||||
this.clusterDetailQueryProcessor = clusterDetailQueryProcessor;
|
||||
this.clusterListQueryProcessor = clusterListQueryProcessor;
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/v1/cluster/list", method = RequestMethod.GET)
|
||||
public ClusterListQueryResult clusters(ClusterListQueryRequest clusterListQueryRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(clusterListQueryProcessor, clusterListQueryRequest,
|
||||
new ClusterListQueryResult());
|
||||
|
||||
return SkyWalkerTemplate.run(clusterListQueryProcessor, clusterListQueryRequest, new ClusterListQueryResult());
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/cluster/detail", method = RequestMethod.GET)
|
||||
public ClusterDetailQueryResult getByTaskId(ClusterDetailQueryRequest clusterDetailQueryRequest) {
|
||||
|
||||
|
||||
return SkyWalkerTemplate.run(clusterDetailQueryProcessor, clusterDetailQueryRequest,
|
||||
new ClusterDetailQueryResult());
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/cluster/delete", method = RequestMethod.DELETE)
|
||||
public ClusterDeleteResult deleteTask(@RequestBody ClusterDeleteRequest clusterDeleteRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(clusterDeleteProcessor, clusterDeleteRequest,
|
||||
new ClusterDeleteResult());
|
||||
|
||||
public ClusterDeleteResult deleteCluster(ClusterDeleteRequest clusterDeleteRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(clusterDeleteProcessor, clusterDeleteRequest, new ClusterDeleteResult());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/cluster/add", method = RequestMethod.POST)
|
||||
public ClusterAddResult clusterAdd(@RequestBody ClusterAddRequest clusterAddRequest) {
|
||||
|
||||
return SkyWalkerTemplate
|
||||
.run(clusterAddProcessor, clusterAddRequest, new ClusterAddResult());
|
||||
|
||||
return SkyWalkerTemplate.run(clusterAddProcessor, clusterAddRequest, new ClusterAddResult());
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/cluster/types", method = RequestMethod.GET)
|
||||
public ClusterTypeResult getClusterType() {
|
||||
|
||||
|
||||
return new ClusterTypeResult(ClusterTypeEnum.getClusterTypeCodes());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.api;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -37,39 +38,41 @@ import com.alibaba.nacossync.template.processor.ConfigQueryProcessor;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SystemConfigApi.java, v 0.1 2018-09-26 上午2:06 NacosSync Exp $$
|
||||
* @version $Id: SystemConfigApi.java, v 0.1 2018-09-26 AM2:06 NacosSync Exp $$
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class SystemConfigApi {
|
||||
|
||||
@Autowired
|
||||
private ConfigQueryProcessor configQueryProcessor;
|
||||
|
||||
@Autowired
|
||||
private ConfigDeleteProcessor configDeleteProcessor;
|
||||
|
||||
@Autowired
|
||||
private ConfigAddProcessor configAddProcessor;
|
||||
|
||||
|
||||
private final ConfigQueryProcessor configQueryProcessor;
|
||||
|
||||
private final ConfigDeleteProcessor configDeleteProcessor;
|
||||
|
||||
private final ConfigAddProcessor configAddProcessor;
|
||||
|
||||
public SystemConfigApi(ConfigQueryProcessor configQueryProcessor, ConfigDeleteProcessor configDeleteProcessor,
|
||||
ConfigAddProcessor configAddProcessor) {
|
||||
this.configQueryProcessor = configQueryProcessor;
|
||||
this.configDeleteProcessor = configDeleteProcessor;
|
||||
this.configAddProcessor = configAddProcessor;
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/v1/systemconfig/list", method = RequestMethod.GET)
|
||||
public ConfigQueryResult tasks(ConfigQueryRequest configQueryRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(configQueryProcessor, configQueryRequest,
|
||||
new ConfigQueryResult());
|
||||
|
||||
return SkyWalkerTemplate.run(configQueryProcessor, configQueryRequest, new ConfigQueryResult());
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/systemconfig/delete", method = RequestMethod.DELETE)
|
||||
public ConfigDeleteResult deleteTask(@RequestBody ConfigDeleteRequest configDeleteRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(configDeleteProcessor, configDeleteRequest,
|
||||
new ConfigDeleteResult());
|
||||
|
||||
return SkyWalkerTemplate.run(configDeleteProcessor, configDeleteRequest, new ConfigDeleteResult());
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/systemconfig/add", method = RequestMethod.POST)
|
||||
public ConfigAddResult taskAdd(@RequestBody ConfigAddRequest configAddRequest) {
|
||||
|
||||
|
||||
return SkyWalkerTemplate.run(configAddProcessor, configAddRequest, new ConfigAddResult());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,89 +1,127 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.api;
|
||||
|
||||
import com.alibaba.nacossync.pojo.request.*;
|
||||
import com.alibaba.nacossync.template.processor.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.alibaba.nacossync.pojo.request.TaskAddAllRequest;
|
||||
import com.alibaba.nacossync.pojo.request.TaskAddRequest;
|
||||
import com.alibaba.nacossync.pojo.request.TaskDeleteInBatchRequest;
|
||||
import com.alibaba.nacossync.pojo.request.TaskDeleteRequest;
|
||||
import com.alibaba.nacossync.pojo.request.TaskDetailQueryRequest;
|
||||
import com.alibaba.nacossync.pojo.request.TaskListQueryRequest;
|
||||
import com.alibaba.nacossync.pojo.request.TaskUpdateRequest;
|
||||
import com.alibaba.nacossync.pojo.result.BaseResult;
|
||||
import com.alibaba.nacossync.pojo.result.TaskAddResult;
|
||||
import com.alibaba.nacossync.pojo.result.TaskDetailQueryResult;
|
||||
import com.alibaba.nacossync.pojo.result.TaskListQueryResult;
|
||||
import com.alibaba.nacossync.template.SkyWalkerTemplate;
|
||||
import com.alibaba.nacossync.template.processor.TaskAddAllProcessor;
|
||||
import com.alibaba.nacossync.template.processor.TaskAddProcessor;
|
||||
import com.alibaba.nacossync.template.processor.TaskDeleteInBatchProcessor;
|
||||
import com.alibaba.nacossync.template.processor.TaskDeleteProcessor;
|
||||
import com.alibaba.nacossync.template.processor.TaskDetailProcessor;
|
||||
import com.alibaba.nacossync.template.processor.TaskListQueryProcessor;
|
||||
import com.alibaba.nacossync.template.processor.TaskUpdateProcessor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: Task.java, v 0.1 2018-09-24 下午3:43 NacosSync Exp $$
|
||||
* @version $Id: Task.java, v 0.1 2018-09-24 PM3:43 NacosSync Exp $$
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class TaskApi {
|
||||
|
||||
@Autowired
|
||||
private TaskUpdateProcessor taskUpdateProcessor;
|
||||
|
||||
@Autowired
|
||||
private TaskAddProcessor taskAddProcessor;
|
||||
|
||||
@Autowired
|
||||
private TaskDeleteProcessor taskDeleteProcessor;
|
||||
|
||||
@Autowired
|
||||
private TaskListQueryProcessor taskListQueryProcessor;
|
||||
|
||||
@Autowired
|
||||
private TaskDetailProcessor taskDetailProcessor;
|
||||
|
||||
|
||||
private final TaskUpdateProcessor taskUpdateProcessor;
|
||||
|
||||
private final TaskAddProcessor taskAddProcessor;
|
||||
|
||||
private final TaskAddAllProcessor taskAddAllProcessor;
|
||||
|
||||
private final TaskDeleteProcessor taskDeleteProcessor;
|
||||
|
||||
private final TaskDeleteInBatchProcessor taskDeleteInBatchProcessor;
|
||||
|
||||
private final TaskListQueryProcessor taskListQueryProcessor;
|
||||
|
||||
private final TaskDetailProcessor taskDetailProcessor;
|
||||
|
||||
public TaskApi(TaskUpdateProcessor taskUpdateProcessor, TaskAddProcessor taskAddProcessor,
|
||||
TaskAddAllProcessor taskAddAllProcessor, TaskDeleteProcessor taskDeleteProcessor,
|
||||
TaskDeleteInBatchProcessor taskDeleteInBatchProcessor, TaskListQueryProcessor taskListQueryProcessor,
|
||||
TaskDetailProcessor taskDetailProcessor) {
|
||||
this.taskUpdateProcessor = taskUpdateProcessor;
|
||||
this.taskAddProcessor = taskAddProcessor;
|
||||
this.taskAddAllProcessor = taskAddAllProcessor;
|
||||
this.taskDeleteProcessor = taskDeleteProcessor;
|
||||
this.taskDeleteInBatchProcessor = taskDeleteInBatchProcessor;
|
||||
this.taskListQueryProcessor = taskListQueryProcessor;
|
||||
this.taskDetailProcessor = taskDetailProcessor;
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/v1/task/list", method = RequestMethod.GET)
|
||||
public TaskListQueryResult tasks(TaskListQueryRequest taskListQueryRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(taskListQueryProcessor, taskListQueryRequest,
|
||||
new TaskListQueryResult());
|
||||
|
||||
return SkyWalkerTemplate.run(taskListQueryProcessor, taskListQueryRequest, new TaskListQueryResult());
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/task/detail", method = RequestMethod.GET)
|
||||
public TaskDetailQueryResult getByTaskId(TaskDetailQueryRequest taskDetailQueryRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(taskDetailProcessor, taskDetailQueryRequest,
|
||||
new TaskDetailQueryResult());
|
||||
|
||||
return SkyWalkerTemplate.run(taskDetailProcessor, taskDetailQueryRequest, new TaskDetailQueryResult());
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(path = "/v1/task/delete", method = RequestMethod.DELETE)
|
||||
public BaseResult deleteTask(@RequestBody TaskDeleteRequest taskDeleteRequest) {
|
||||
|
||||
public BaseResult deleteTask(TaskDeleteRequest taskDeleteRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(taskDeleteProcessor, taskDeleteRequest, new BaseResult());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param taskBatchDeleteRequest
|
||||
* @return
|
||||
* @author yongchao9
|
||||
*/
|
||||
@RequestMapping(path = "/v1/task/deleteInBatch", method = RequestMethod.DELETE)
|
||||
public BaseResult batchDeleteTask(TaskDeleteInBatchRequest taskBatchDeleteRequest) {
|
||||
return SkyWalkerTemplate.run(taskDeleteInBatchProcessor, taskBatchDeleteRequest, new BaseResult());
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/v1/task/add", method = RequestMethod.POST)
|
||||
public BaseResult taskAdd(@RequestBody TaskAddRequest addTaskRequest) {
|
||||
|
||||
|
||||
return SkyWalkerTemplate.run(taskAddProcessor, addTaskRequest, new TaskAddResult());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO 目前仅支持 Nacos 为源的同步类型,待完善更多类型支持.
|
||||
* <p>
|
||||
* 支持从 sourceCluster 获取所有 service,然后生成同步到 destCluster 的任务。
|
||||
* </p>
|
||||
*/
|
||||
@RequestMapping(path = "/v1/task/addAll", method = RequestMethod.POST)
|
||||
public BaseResult taskAddAll(@RequestBody TaskAddAllRequest addAllRequest) {
|
||||
|
||||
return SkyWalkerTemplate.run(taskAddAllProcessor, addAllRequest, new TaskAddResult());
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/v1/task/update", method = RequestMethod.POST)
|
||||
public BaseResult updateTask(@RequestBody TaskUpdateRequest taskUpdateRequest) {
|
||||
|
||||
|
||||
return SkyWalkerTemplate.run(taskUpdateProcessor, taskUpdateRequest, new BaseResult());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,19 +16,7 @@
|
|||
*/
|
||||
package com.alibaba.nacossync.cache;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.TypeReference;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.dao.ClusterAccessService;
|
||||
import com.alibaba.nacossync.exception.SkyWalkerException;
|
||||
|
@ -36,33 +24,51 @@ import com.alibaba.nacossync.pojo.FinishedTask;
|
|||
import com.alibaba.nacossync.pojo.model.ClusterDO;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.SkyWalkerUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SkyWalkerCacheServices.java, v 0.1 2018-09-27 上午2:47 NacosSync Exp $$
|
||||
* @version $Id: SkyWalkerCacheServices.java, v 0.1 2018-09-27 AM2:47 NacosSync Exp $$
|
||||
*/
|
||||
@Service
|
||||
public class SkyWalkerCacheServices {
|
||||
|
||||
private static final Map<String, FinishedTask> FINISHED_TASK_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private final ClusterAccessService clusterAccessService;
|
||||
|
||||
@Autowired
|
||||
private ClusterAccessService clusterAccessService;
|
||||
|
||||
private static Map<String, FinishedTask> finishedTaskMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public SkyWalkerCacheServices(ClusterAccessService clusterAccessService, ObjectMapper objectMapper) {
|
||||
this.clusterAccessService = clusterAccessService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public String getClusterConnectKey(String clusterId) {
|
||||
List<String> allClusterConnectKey = getAllClusterConnectKey(clusterId);
|
||||
return allClusterConnectKey.get(ThreadLocalRandom.current().nextInt(allClusterConnectKey.size()));
|
||||
}
|
||||
|
||||
ClusterDO clusterDOS = clusterAccessService.findByClusterId(clusterId);
|
||||
@SneakyThrows
|
||||
public List<String> getAllClusterConnectKey(String clusterId) {
|
||||
ClusterDO clusterDO = clusterAccessService.findByClusterId(clusterId);
|
||||
|
||||
List<String> connectKeyList = JSONObject.parseObject(clusterDOS.getConnectKeyList(),
|
||||
new TypeReference<List<String>>() {
|
||||
});
|
||||
List<String> connectKeyList = objectMapper.readerForListOf(String.class)
|
||||
.readValue(clusterDO.getConnectKeyList());
|
||||
|
||||
if (CollectionUtils.isEmpty(connectKeyList)) {
|
||||
throw new SkyWalkerException("getClusterConnectKey empty, clusterId:" + clusterId);
|
||||
}
|
||||
|
||||
Random random = new Random();
|
||||
return connectKeyList.get(random.nextInt(connectKeyList.size()));
|
||||
return connectKeyList;
|
||||
}
|
||||
|
||||
public ClusterTypeEnum getClusterType(String clusterId) {
|
||||
|
@ -79,25 +85,32 @@ public class SkyWalkerCacheServices {
|
|||
FinishedTask finishedTask = new FinishedTask();
|
||||
finishedTask.setOperationId(operationId);
|
||||
|
||||
finishedTaskMap.put(operationId, finishedTask);
|
||||
|
||||
FINISHED_TASK_MAP.put(operationId, finishedTask);
|
||||
}
|
||||
|
||||
public FinishedTask getFinishedTask(TaskDO taskDO) {
|
||||
|
||||
String operationId = SkyWalkerUtil.getOperationId(taskDO);
|
||||
|
||||
if (StringUtils.isEmpty(operationId)) {
|
||||
if (!StringUtils.hasLength(operationId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return finishedTaskMap.get(operationId);
|
||||
|
||||
return FINISHED_TASK_MAP.get(operationId);
|
||||
}
|
||||
|
||||
|
||||
public void removeFinishedTask(String operationId) {
|
||||
if (!StringUtils.hasLength(operationId)) {
|
||||
return;
|
||||
}
|
||||
FINISHED_TASK_MAP.remove(operationId);
|
||||
}
|
||||
|
||||
public Map<String, FinishedTask> getFinishedTaskMap() {
|
||||
|
||||
return finishedTaskMap;
|
||||
return FINISHED_TASK_MAP;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.constant;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -23,26 +21,30 @@ import java.util.List;
|
|||
* @author NacosSync
|
||||
* @version $Id: ClusterTypeEnum.java, v 0.1 2018-09-25 下午4:38 NacosSync Exp $$
|
||||
*/
|
||||
@Getter
|
||||
public enum ClusterTypeEnum {
|
||||
|
||||
CS("CS", "configserver集群"),
|
||||
|
||||
NACOS("NACOS", "nacos集群");
|
||||
NACOS("NACOS", "nacos集群"),
|
||||
|
||||
//CONSUL("CONSUL", "consul集群");
|
||||
EUREKA("EUREKA", "eureka集群"),
|
||||
|
||||
private String code;
|
||||
CONSUL("CONSUL", "consul集群"),
|
||||
|
||||
ZK("ZK", "zookeeper集群");
|
||||
|
||||
|
||||
private final String code;
|
||||
|
||||
private String desc;
|
||||
|
||||
ClusterTypeEnum(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public static List<String> getClusterTypeCodes() {
|
||||
|
||||
List<String> list = new ArrayList<String>();
|
||||
List<String> list = new ArrayList<>();
|
||||
|
||||
for (ClusterTypeEnum clusterTypeEnum : ClusterTypeEnum.values()) {
|
||||
list.add(clusterTypeEnum.getCode());
|
||||
|
@ -50,42 +52,7 @@ public enum ClusterTypeEnum {
|
|||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>code</tt>.
|
||||
*
|
||||
* @return property value of code
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>code </tt>.
|
||||
*
|
||||
* @param code value to be assigned to property code
|
||||
*/
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>desc</tt>.
|
||||
*
|
||||
* @return property value of desc
|
||||
*/
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>desc </tt>.
|
||||
*
|
||||
* @param desc value to be assigned to property desc
|
||||
*/
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
|
||||
public static boolean contains(String clusterType) {
|
||||
|
||||
for (ClusterTypeEnum clusterTypeEnum : ClusterTypeEnum.values()) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Alipay.com Inc. Copyright (c) 2004-2019 All Rights Reserved.
|
||||
*/
|
||||
package com.alibaba.nacossync.constant;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: MetricsStatisticsType.java, v 0.1 2019年02月28日 下午2:17 NacosSync Exp $
|
||||
*/
|
||||
@Getter
|
||||
public enum MetricsStatisticsType {
|
||||
|
||||
CACHE_SIZE("nacosSync.finished.taskMap.cacheSize", "任务执行完成缓存列表数"),
|
||||
|
||||
TASK_SIZE("nacosSync.task.size", "同步任务数"),
|
||||
|
||||
CLUSTER_SIZE("nacosSync.cluster.size", "集群数"),
|
||||
|
||||
SYNC_TASK_RT("nacosSync.add.task.rt", "同步任务执行耗时"),
|
||||
|
||||
DELETE_TASK_RT("nacosSync.delete.task.rt", "删除任务耗时"),
|
||||
|
||||
DISPATCHER_TASK("nacosSync.dispatcher.task", "从数据库中分发任务"),
|
||||
|
||||
SYNC_ERROR("nacosSync.sync.task.error", "所有同步执行时的异常"),
|
||||
|
||||
DELETE_ERROR("nacosSync.delete.task.error", "所有删除同步执行时的异常");
|
||||
|
||||
/**
|
||||
* metricsName
|
||||
*/
|
||||
private final String metricsName;
|
||||
|
||||
MetricsStatisticsType(String code, String desc) {
|
||||
this.metricsName = code;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,46 +16,23 @@
|
|||
*/
|
||||
package com.alibaba.nacossync.constant;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: ResultCodeEnum.java, v 0.1 2018-09-25 下午4:38 NacosSync Exp $$
|
||||
* @version $Id: ResultCodeEnum.java, v 0.1 2018-09-25 PM4:38 NacosSync Exp $$
|
||||
*/
|
||||
@Getter
|
||||
public enum ResultCodeEnum {
|
||||
|
||||
SUCCESS("SUCCESS", "请求成功", "请求成功"),
|
||||
SYSTEM_ERROR("SYSTEM_ERROR", "系统异常", "系统异常");
|
||||
|
||||
private String code;
|
||||
private String errorMessage;
|
||||
private String detail;
|
||||
|
||||
private final String code;
|
||||
|
||||
ResultCodeEnum(String code, String errorMessage, String detail) {
|
||||
this.code = code;
|
||||
this.errorMessage = errorMessage;
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public String getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public void setDetail(String detail) {
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -18,15 +18,24 @@ package com.alibaba.nacossync.constant;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SkyWalkerConstants.java, v 0.1 2018-09-26 上午12:07 NacosSync Exp $$
|
||||
* @version $Id: SkyWalkerConstants.java, v 0.1 2018-09-26 AM12:07 NacosSync Exp $$
|
||||
*/
|
||||
public class SkyWalkerConstants {
|
||||
|
||||
public final static String UNDERLINE = "_";
|
||||
public static final String UNDERLINE = "_";
|
||||
|
||||
public final static String DEST_CLUSTERID_KEY = "destClusterId";
|
||||
public final static String GROUP_NAME = "groupName";
|
||||
public final static String SYNC_SOURCE_KEY = "syncSource";
|
||||
public final static String SOURCE_CLUSTERID_KEY = "sourceClusterId";
|
||||
public static final String DEST_CLUSTER_ID_KEY = "destClusterId";
|
||||
public static final String GROUP_NAME = "groupName";
|
||||
public static final String SYNC_SOURCE_KEY = "syncSource";
|
||||
public static final String SOURCE_CLUSTER_ID_KEY = "sourceClusterId";
|
||||
public static final String MANAGEMENT_PORT_KEY="management.port";
|
||||
public static final String MANAGEMENT_CONTEXT_PATH_KEY="management.context-path";
|
||||
|
||||
public static final String SERVICE_NAME_PARAM="serviceNameParam";
|
||||
public static final String GROUP_NAME_PARAM="groupNameParam";
|
||||
public static final String PAGE_NO="pageNo";
|
||||
public static final String PAGE_SIZE="pageSize";
|
||||
public static final String SYNC_INSTANCE_TAG="sync.instance.tag";
|
||||
public static final String NACOS_ALL_SERVICE_NAME = "ALL";
|
||||
|
||||
}
|
||||
|
|
|
@ -16,61 +16,34 @@
|
|||
*/
|
||||
package com.alibaba.nacossync.constant;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: TaskStatusEnum.java, v 0.1 2018-09-26 上午2:38 NacosSync Exp $$
|
||||
*/
|
||||
public enum TaskStatusEnum {
|
||||
|
||||
/** */
|
||||
/**
|
||||
* synchronization of task
|
||||
*/
|
||||
SYNC("SYNC", "任务同步"),
|
||||
/** */
|
||||
/**
|
||||
* delete the task
|
||||
*/
|
||||
DELETE("DELETE", "任务需要被删除");
|
||||
|
||||
|
||||
private String code;
|
||||
private String desc;
|
||||
@Getter
|
||||
private final String code;
|
||||
private final String desc;
|
||||
|
||||
TaskStatusEnum(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>code</tt>.
|
||||
*
|
||||
* @return property value of code
|
||||
*/
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>code </tt>.
|
||||
*
|
||||
* @param code value to be assigned to property code
|
||||
*/
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method for property <tt>desc</tt>.
|
||||
*
|
||||
* @return property value of desc
|
||||
*/
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter method for property <tt>desc </tt>.
|
||||
*
|
||||
* @param desc value to be assigned to property desc
|
||||
*/
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean contains(String code) {
|
||||
|
||||
for (TaskStatusEnum taskStatusEnum : TaskStatusEnum.values()) {
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
*/
|
||||
package com.alibaba.nacossync.dao;
|
||||
|
||||
import com.alibaba.nacossync.dao.repository.ClusterRepository;
|
||||
import com.alibaba.nacossync.pojo.QueryCondition;
|
||||
import com.alibaba.nacossync.pojo.model.ClusterDO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
@ -27,9 +27,6 @@ import org.springframework.data.domain.Sort;
|
|||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.nacossync.dao.repository.ClusterRepository;
|
||||
import com.alibaba.nacossync.pojo.model.ClusterDO;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
@ -38,15 +35,18 @@ import java.util.List;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: ClusterAccessService.java, v 0.1 2018-09-25 下午9:32 NacosSync Exp $$
|
||||
* @version $Id: ClusterAccessService.java, v 0.1 2018-09-25 PM9:32 NacosSync Exp $$
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ClusterAccessService implements PageQueryService<ClusterDO> {
|
||||
|
||||
@Autowired
|
||||
private ClusterRepository clusterRepository;
|
||||
|
||||
private final ClusterRepository clusterRepository;
|
||||
|
||||
public ClusterAccessService(ClusterRepository clusterRepository) {
|
||||
this.clusterRepository = clusterRepository;
|
||||
}
|
||||
|
||||
public ClusterDO insert(ClusterDO clusterDO) {
|
||||
|
||||
return clusterRepository.save(clusterDO);
|
||||
|
@ -103,4 +103,12 @@ public class ClusterAccessService implements PageQueryService<ClusterDO> {
|
|||
predicates.add(criteriaBuilder.like(root.get("clusterName"), "%" + queryCondition.getServiceName() + "%"));
|
||||
return predicates;
|
||||
}
|
||||
|
||||
public int findClusterLevel(String sourceClusterId){
|
||||
ClusterDO clusterDO = clusterRepository.findByClusterId(sourceClusterId);
|
||||
if (clusterDO != null) {
|
||||
return clusterDO.getClusterLevel();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import org.springframework.data.domain.Page;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: PageQueryService.java, v 0.1 2018年11月05日 下午5:51 NacosSync Exp $
|
||||
* @version $Id: PageQueryService.java, v 0.1 2018年11月05日 PM5:51 NacosSync Exp $
|
||||
*/
|
||||
public interface PageQueryService<T> {
|
||||
|
||||
|
|
|
@ -14,10 +14,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.dao;
|
||||
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.dao.repository.TaskRepository;
|
||||
import com.alibaba.nacossync.pojo.QueryCondition;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
@ -25,9 +28,6 @@ import org.springframework.data.domain.Sort;
|
|||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.nacossync.dao.repository.TaskRepository;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
@ -36,73 +36,98 @@ import java.util.List;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: TaskAccessService.java, v 0.1 2018-09-25 上午12:07 NacosSync Exp $$
|
||||
* @version $Id: TaskAccessService.java, v 0.1 2018-09-25 AM12:07 NacosSync Exp $$
|
||||
*/
|
||||
@Service
|
||||
public class TaskAccessService implements PageQueryService<TaskDO> {
|
||||
|
||||
@Autowired
|
||||
private TaskRepository taskRepository;
|
||||
|
||||
|
||||
private final TaskRepository taskRepository;
|
||||
|
||||
public TaskAccessService(TaskRepository taskRepository) {
|
||||
this.taskRepository = taskRepository;
|
||||
}
|
||||
|
||||
public TaskDO findByTaskId(String taskId) {
|
||||
|
||||
|
||||
return taskRepository.findByTaskId(taskId);
|
||||
}
|
||||
|
||||
|
||||
public void deleteTaskById(String taskId) {
|
||||
taskRepository.deleteByTaskId(taskId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* batch delete tasks by taskIds
|
||||
*
|
||||
* @param taskIds
|
||||
* @author yongchao9
|
||||
*/
|
||||
public void deleteTaskInBatch(List<String> taskIds) {
|
||||
List<TaskDO> tds = taskRepository.findAllByTaskIdIn(taskIds);
|
||||
taskRepository.deleteAllInBatch(tds);
|
||||
}
|
||||
|
||||
public Iterable<TaskDO> findAll() {
|
||||
|
||||
|
||||
return taskRepository.findAll();
|
||||
}
|
||||
|
||||
|
||||
public void addTask(TaskDO taskDO) {
|
||||
|
||||
|
||||
taskRepository.save(taskDO);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public int countByDestClusterIdOrSourceClusterId(String destClusterId, String sourceClusterId) {
|
||||
return taskRepository.countByDestClusterIdOrSourceClusterId(destClusterId, sourceClusterId);
|
||||
}
|
||||
|
||||
private Predicate getPredicate(CriteriaBuilder criteriaBuilder, List<Predicate> predicates) {
|
||||
Predicate[] p = new Predicate[predicates.size()];
|
||||
return criteriaBuilder.and(predicates.toArray(p));
|
||||
}
|
||||
|
||||
private List<Predicate> getPredicates(Root<TaskDO> root, CriteriaBuilder criteriaBuilder, QueryCondition queryCondition) {
|
||||
|
||||
|
||||
private List<Predicate> getPredicates(Root<TaskDO> root, CriteriaBuilder criteriaBuilder,
|
||||
QueryCondition queryCondition) {
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
predicates.add(criteriaBuilder.like(root.get("serviceName"), "%" + queryCondition.getServiceName() + "%"));
|
||||
|
||||
|
||||
return predicates;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Page<TaskDO> findPageNoCriteria(Integer pageNum, Integer size) {
|
||||
|
||||
|
||||
Pageable pageable = PageRequest.of(pageNum, size, Sort.Direction.DESC, "id");
|
||||
|
||||
|
||||
return taskRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Page<TaskDO> findPageCriteria(Integer pageNum, Integer size, QueryCondition queryCondition) {
|
||||
|
||||
|
||||
Pageable pageable = PageRequest.of(pageNum, size, Sort.Direction.DESC, "id");
|
||||
|
||||
|
||||
return getTaskDOS(queryCondition, pageable);
|
||||
}
|
||||
|
||||
|
||||
private Page<TaskDO> getTaskDOS(QueryCondition queryCondition, Pageable pageable) {
|
||||
return taskRepository.findAll(
|
||||
(Specification<TaskDO>) (root, criteriaQuery, criteriaBuilder) -> {
|
||||
|
||||
List<Predicate> predicates = getPredicates(root,
|
||||
criteriaBuilder, queryCondition);
|
||||
|
||||
return getPredicate(criteriaBuilder, predicates);
|
||||
|
||||
}, pageable);
|
||||
return taskRepository.findAll((Specification<TaskDO>) (root, criteriaQuery, criteriaBuilder) -> {
|
||||
|
||||
List<Predicate> predicates = getPredicates(root, criteriaBuilder, queryCondition);
|
||||
|
||||
return getPredicate(criteriaBuilder, predicates);
|
||||
|
||||
}, pageable);
|
||||
}
|
||||
|
||||
|
||||
public List<TaskDO> findAllByServiceNameEqualAll() {
|
||||
return taskRepository.findAllByServiceNameEqualsIgnoreCase(SkyWalkerConstants.NACOS_ALL_SERVICE_NAME);
|
||||
}
|
||||
|
||||
public List<TaskDO> findAllByServiceNameNotEqualAll() {
|
||||
return taskRepository.findAllByServiceNameNotIgnoreCase(SkyWalkerConstants.NACOS_ALL_SERVICE_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,17 +16,17 @@
|
|||
*/
|
||||
package com.alibaba.nacossync.dao.repository;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import com.alibaba.nacossync.pojo.model.ClusterDO;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import com.alibaba.nacossync.pojo.model.ClusterDO;
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: ClusterRepository.java, v 0.1 2018-09-25 下午9:31 NacosSync Exp $$
|
||||
* @version $Id: ClusterRepository.java, v 0.1 2018-09-25 PM9:31 NacosSync Exp $$
|
||||
*/
|
||||
public interface ClusterRepository extends CrudRepository<ClusterDO, Integer>, JpaRepository<ClusterDO, Integer>,
|
||||
JpaSpecificationExecutor<ClusterDO> {
|
||||
|
@ -34,6 +34,6 @@ public interface ClusterRepository extends CrudRepository<ClusterDO, Integer>, J
|
|||
ClusterDO findByClusterId(String clusterId);
|
||||
|
||||
@Transactional
|
||||
int deleteByClusterId(String clusterId);
|
||||
void deleteByClusterId(String clusterId);
|
||||
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import com.alibaba.nacossync.pojo.model.SystemConfigDO;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SystemConfigRepository.java, v 0.1 2018-09-25 下午9:31 NacosSync Exp $$
|
||||
* @version $Id: SystemConfigRepository.java, v 0.1 2018-09-25 PM9:31 NacosSync Exp $$
|
||||
*/
|
||||
public interface SystemConfigRepository extends CrudRepository<SystemConfigDO, Integer>,
|
||||
JpaSpecificationExecutor<SystemConfigDO> {
|
||||
|
|
|
@ -16,19 +16,17 @@
|
|||
*/
|
||||
package com.alibaba.nacossync.dao.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: TaskRepository.java, v 0.1 2018-09-25 上午12:04 NacosSync Exp $$
|
||||
* @version $Id: TaskRepository.java, v 0.1 2018-09-25 AM12:04 NacosSync Exp $$
|
||||
*/
|
||||
public interface TaskRepository extends CrudRepository<TaskDO, Integer>, JpaRepository<TaskDO, Integer>,
|
||||
JpaSpecificationExecutor<TaskDO> {
|
||||
|
@ -36,8 +34,15 @@ public interface TaskRepository extends CrudRepository<TaskDO, Integer>, JpaRepo
|
|||
TaskDO findByTaskId(String taskId);
|
||||
|
||||
@Transactional
|
||||
int deleteByTaskId(String taskId);
|
||||
|
||||
List<TaskDO> getAllByWorkerIp(String workerIp);
|
||||
void deleteByTaskId(String taskId);
|
||||
|
||||
List<TaskDO> findAllByTaskIdIn(List<String> taskIds);
|
||||
/**
|
||||
* query service is all,use ns leven sync data
|
||||
*/
|
||||
List<TaskDO> findAllByServiceNameEqualsIgnoreCase(String serviceName);
|
||||
List<TaskDO> findAllByServiceNameNotIgnoreCase(String serviceName);
|
||||
|
||||
int countByDestClusterIdOrSourceClusterId(String destClusterId,String sourceClusterId);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.alibaba.nacossync.event;
|
||||
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeleteAllSubTaskEvent {
|
||||
public DeleteAllSubTaskEvent(TaskDO taskDO) {
|
||||
this.taskDO = taskDO;
|
||||
}
|
||||
|
||||
private final TaskDO taskDO;
|
||||
}
|
|
@ -22,7 +22,7 @@ import com.alibaba.nacossync.pojo.model.TaskDO;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: DeleteTaskEvent.java, v 0.1 2018-09-27 上午2:38 NacosSync Exp $$
|
||||
* @version $Id: DeleteTaskEvent.java, v 0.1 2018-09-27 AM2:38 NacosSync Exp $$
|
||||
*/
|
||||
@Data
|
||||
public class DeleteTaskEvent {
|
||||
|
|
|
@ -22,7 +22,7 @@ import com.alibaba.nacossync.pojo.model.TaskDO;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: EventModel.java, v 0.1 2018-09-27 上午1:41 NacosSync Exp $$
|
||||
* @version $Id: EventModel.java, v 0.1 2018-09-27 AM1:41 NacosSync Exp $$
|
||||
*/
|
||||
@Data
|
||||
public class SyncTaskEvent {
|
||||
|
|
|
@ -14,70 +14,83 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.event.listener;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.MetricsStatisticsType;
|
||||
import com.alibaba.nacossync.event.DeleteTaskEvent;
|
||||
import com.alibaba.nacossync.event.SyncTaskEvent;
|
||||
import com.alibaba.nacossync.extension.SyncManagerService;
|
||||
import com.alibaba.nacossync.monitor.MetricsManager;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: EventListener.java, v 0.1 2018-09-27 上午1:21 NacosSync Exp $$
|
||||
* @version $Id: EventListener.java, v 0.1 2018-09-27 AM1:21 NacosSync Exp $$
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class EventListener {
|
||||
|
||||
@Autowired
|
||||
private SyncManagerService syncManagerService;
|
||||
|
||||
@Autowired
|
||||
private EventBus eventBus;
|
||||
|
||||
@Autowired
|
||||
private SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
|
||||
private final MetricsManager metricsManager;
|
||||
|
||||
private final SyncManagerService syncManagerService;
|
||||
|
||||
private final EventBus eventBus;
|
||||
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
public EventListener(MetricsManager metricsManager, SyncManagerService syncManagerService, EventBus eventBus,
|
||||
SkyWalkerCacheServices skyWalkerCacheServices) {
|
||||
this.metricsManager = metricsManager;
|
||||
this.syncManagerService = syncManagerService;
|
||||
this.eventBus = eventBus;
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
eventBus.register(this);
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
public void listenerSyncTaskEvent(SyncTaskEvent syncTaskEvent) {
|
||||
|
||||
public void sync(SyncTaskEvent syncTaskEvent) {
|
||||
|
||||
try {
|
||||
|
||||
syncManagerService.sync(syncTaskEvent.getTaskDO());
|
||||
skyWalkerCacheServices.addFinishedTask(syncTaskEvent.getTaskDO());
|
||||
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
if (syncManagerService.sync(syncTaskEvent.getTaskDO(), null)) {
|
||||
skyWalkerCacheServices.addFinishedTask(syncTaskEvent.getTaskDO());
|
||||
metricsManager.record(MetricsStatisticsType.SYNC_TASK_RT, stopwatch.elapsed().toMillis());
|
||||
} else {
|
||||
log.warn("syncTaskEvent process error");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("listenerSyncTaskEvent process error", e);
|
||||
log.warn("syncTaskEvent process error", e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
public void listenerDeleteTaskEvent(DeleteTaskEvent deleteTaskEvent) {
|
||||
|
||||
public void delete(DeleteTaskEvent deleteTaskEvent) {
|
||||
|
||||
try {
|
||||
|
||||
syncManagerService.delete(deleteTaskEvent.getTaskDO());
|
||||
skyWalkerCacheServices.addFinishedTask(deleteTaskEvent.getTaskDO());
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
if (syncManagerService.delete(deleteTaskEvent.getTaskDO())) {
|
||||
skyWalkerCacheServices.removeFinishedTask(deleteTaskEvent.getTaskDO().getOperationId());
|
||||
metricsManager.record(MetricsStatisticsType.DELETE_TASK_RT, stopwatch.elapsed().toMillis());
|
||||
} else {
|
||||
log.warn("deleteTaskEvent delete failure");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("listenerDeleteTaskEvent process error", e);
|
||||
log.warn("deleteTaskEvent delete failure.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import com.alibaba.nacossync.constant.ResultCodeEnum;
|
|||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SkyWalkerException.java, v 0.1 2018-09-30 上午10:09 NacosSync Exp $$
|
||||
* @version $Id: SkyWalkerException.java, v 0.1 2018-09-30 AM10:09 NacosSync Exp $$
|
||||
*/
|
||||
public class SkyWalkerException extends RuntimeException {
|
||||
/**
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package com.alibaba.nacossync.extension;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author yangyshdan
|
||||
* @version $Id: ConfigServerSyncManagerService.java, v 0.1 2018-11-12 下午5:17 NacosSync Exp $$
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class NacosSyncService {
|
||||
|
||||
}
|
|
@ -1,63 +1,88 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.alibaba.nacossync.util.SkyWalkerUtil.generateSyncKey;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SyncManagerService.java, v 0.1 2018-09-25 下午5:17 NacosSync Exp $$
|
||||
* @version $Id: SyncManagerService.java, v 0.1 2018-09-25 PM5:17 NacosSync Exp $$
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SyncManagerService {
|
||||
public class SyncManagerService implements InitializingBean, ApplicationContextAware {
|
||||
|
||||
@Autowired
|
||||
protected SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
protected final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
private Hashtable<ClusterTypeEnum, SyncService> syncServiceMap = new Hashtable<ClusterTypeEnum, SyncService>();
|
||||
private final ConcurrentHashMap<String, SyncService> syncServiceMap = new ConcurrentHashMap<String, SyncService>();
|
||||
|
||||
public void register(ClusterTypeEnum type, SyncService service) {
|
||||
syncServiceMap.put(type, service);
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
public SyncManagerService(
|
||||
SkyWalkerCacheServices skyWalkerCacheServices) {
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
}
|
||||
|
||||
public boolean delete(TaskDO taskDO) throws NacosException {
|
||||
|
||||
ClusterTypeEnum type = this.skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId());
|
||||
return syncServiceMap.get(type).delete(taskDO);
|
||||
return getSyncService(taskDO.getSourceClusterId(), taskDO.getDestClusterId()).delete(taskDO);
|
||||
|
||||
}
|
||||
|
||||
public boolean sync(TaskDO taskDO) {
|
||||
public boolean sync(TaskDO taskDO, Integer index) {
|
||||
|
||||
ClusterTypeEnum type = this.skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId());
|
||||
return getSyncService(taskDO.getSourceClusterId(), taskDO.getDestClusterId()).sync(taskDO, index);
|
||||
|
||||
return syncServiceMap.get(type).sync(taskDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
this.applicationContext.getBeansWithAnnotation(NacosSyncService.class).forEach((key, value) -> {
|
||||
NacosSyncService nacosSyncService = value.getClass().getAnnotation(NacosSyncService.class);
|
||||
ClusterTypeEnum sourceCluster = nacosSyncService.sourceCluster();
|
||||
ClusterTypeEnum destinationCluster = nacosSyncService.destinationCluster();
|
||||
syncServiceMap.put(generateSyncKey(sourceCluster, destinationCluster), (SyncService) value);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public SyncService getSyncService(String sourceClusterId, String destClusterId) {
|
||||
if (StringUtils.isEmpty(sourceClusterId) || StringUtils.isEmpty(destClusterId)) {
|
||||
throw new IllegalArgumentException("Source cluster id and destination cluster id must not be null or empty");
|
||||
}
|
||||
ClusterTypeEnum sourceClusterType = this.skyWalkerCacheServices.getClusterType(sourceClusterId);
|
||||
ClusterTypeEnum destClusterType = this.skyWalkerCacheServices.getClusterType(destClusterId);
|
||||
|
||||
return syncServiceMap.get(generateSyncKey(sourceClusterType, destClusterType));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,61 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension;
|
||||
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author NacosSync
|
||||
* @version $Id: SyncManagerService.java, v 0.1 2018-09-25 下午5:17 NacosSync Exp $$
|
||||
*/
|
||||
public interface SyncService {
|
||||
|
||||
public boolean delete(TaskDO taskDO);
|
||||
/**
|
||||
* delete the sync task
|
||||
*
|
||||
* @param taskDO
|
||||
* @return
|
||||
*/
|
||||
boolean delete(TaskDO taskDO);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param taskDO
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
boolean sync(TaskDO taskDO, Integer index);
|
||||
|
||||
public boolean sync(TaskDO taskDO);
|
||||
/**
|
||||
* Determines that the current instance data is from another source cluster
|
||||
*/
|
||||
default boolean needSync(Map<String, String> sourceMetaData) {
|
||||
boolean syncTag = StringUtils.isBlank(sourceMetaData.get(SkyWalkerConstants.SYNC_INSTANCE_TAG));
|
||||
boolean blank = StringUtils.isBlank(sourceMetaData.get(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY));
|
||||
return syncTag && blank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the source cluster ID of the current instance is the same as the source
|
||||
* cluster ID of the task
|
||||
*/
|
||||
default boolean needDelete(Map<String, String> destMetaData, TaskDO taskDO) {
|
||||
return StringUtils.equals(destMetaData.get(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY),
|
||||
taskDO.getSourceClusterId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.annotation;
|
||||
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.extension.SyncManagerService;
|
||||
import com.alibaba.nacossync.extension.SyncService;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Mainly used to mark the SyncService implementation
|
||||
* of which source cluster to which destination cluster
|
||||
*
|
||||
* @author paderlol
|
||||
* @date 2018-12-31 15:28
|
||||
* @see SyncService
|
||||
* @see SyncManagerService
|
||||
*
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Component
|
||||
public @interface NacosSyncService {
|
||||
|
||||
@AliasFor(annotation = Component.class)
|
||||
String value() default "";
|
||||
|
||||
ClusterTypeEnum sourceCluster();
|
||||
|
||||
ClusterTypeEnum destinationCluster();
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.eureka;
|
||||
|
||||
import com.netflix.appinfo.InstanceInfo;
|
||||
import com.netflix.discovery.shared.transport.EurekaHttpClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
* @author liu jun jie
|
||||
* @date 2019-06-26
|
||||
*/
|
||||
@Slf4j
|
||||
public class EurekaBeatReactor {
|
||||
private final ScheduledExecutorService executorService;
|
||||
|
||||
private static final long CLIENT_BEAT_INTERVAL = 5 * 1000;
|
||||
private final Map<String, InstanceInfo> eurekaBeat = new ConcurrentHashMap<>();
|
||||
private final EurekaHttpClient eurekaHttpClient;
|
||||
|
||||
public EurekaBeatReactor(EurekaHttpClient eurekaHttpClient) {
|
||||
this.eurekaHttpClient = eurekaHttpClient;
|
||||
executorService = new ScheduledThreadPoolExecutor(30, r -> {
|
||||
Thread thread = new Thread(r);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("com.alibaba.nacossync.eureka.beat.sender");
|
||||
return thread;
|
||||
});
|
||||
|
||||
executorService.schedule(new BeatProcessor(), 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void addInstance(String key, InstanceInfo value) {
|
||||
this.eurekaBeat.put(key, value);
|
||||
}
|
||||
|
||||
public void removeInstance(String key) {
|
||||
log.debug("[BEAT] removing beat: {} to beat map.", key);
|
||||
this.eurekaBeat.remove(key);
|
||||
}
|
||||
|
||||
class BeatProcessor implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
for (Map.Entry<String, InstanceInfo> entry : eurekaBeat.entrySet()) {
|
||||
InstanceInfo instanceInfo = entry.getValue();
|
||||
executorService.schedule(() -> {
|
||||
log.debug("[BEAT] adding beat: {} to beat map.", instanceInfo.getId());
|
||||
eurekaHttpClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, InstanceInfo.InstanceStatus.UP);
|
||||
}, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[CLIENT-BEAT] Exception while scheduling beat.", e);
|
||||
} finally {
|
||||
executorService.schedule(this, CLIENT_BEAT_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.eureka;
|
||||
|
||||
import com.netflix.appinfo.InstanceInfo;
|
||||
import com.netflix.discovery.shared.Application;
|
||||
import com.netflix.discovery.shared.transport.EurekaHttpClient;
|
||||
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author liu jun jie
|
||||
* @date 2019-06-26
|
||||
*/
|
||||
public class EurekaNamingService {
|
||||
private final EurekaHttpClient eurekaHttpClient;
|
||||
private final EurekaBeatReactor beatReactor;
|
||||
|
||||
|
||||
public EurekaNamingService(EurekaHttpClient eurekaHttpClient) {
|
||||
this.eurekaHttpClient = eurekaHttpClient;
|
||||
beatReactor = new EurekaBeatReactor(eurekaHttpClient);
|
||||
}
|
||||
|
||||
public void registerInstance(InstanceInfo instanceInfo) {
|
||||
EurekaHttpResponse<Void> response = eurekaHttpClient.register(instanceInfo);
|
||||
if (Objects.requireNonNull(HttpStatus.resolve(response.getStatusCode())).is2xxSuccessful()) {
|
||||
beatReactor.addInstance(instanceInfo.getId(), instanceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void deregisterInstance(InstanceInfo instanceInfo) {
|
||||
EurekaHttpResponse<Void> response = eurekaHttpClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
|
||||
if (Objects.requireNonNull(HttpStatus.resolve(response.getStatusCode())).is2xxSuccessful()) {
|
||||
beatReactor.removeInstance(instanceInfo.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public List<InstanceInfo> getApplications(String serviceName) {
|
||||
EurekaHttpResponse<Application> eurekaHttpResponse =
|
||||
eurekaHttpClient.getApplication(serviceName);
|
||||
if (Objects.requireNonNull(HttpStatus.resolve(eurekaHttpResponse.getStatusCode())).is2xxSuccessful()) {
|
||||
return eurekaHttpResponse.getEntity().getInstances();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.event;
|
||||
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date: 2019-01-12 22:28
|
||||
*/
|
||||
@Data
|
||||
public class SpecialSyncEvent {
|
||||
private TaskDO taskDO;
|
||||
private Consumer<TaskDO> syncAction;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.event;
|
||||
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date 2019-01-12 22:42
|
||||
*/
|
||||
@Service
|
||||
public class SpecialSyncEventBus {
|
||||
private final ConcurrentHashMap<String, SpecialSyncEvent> specialSyncEventRegistry = new ConcurrentHashMap<>();
|
||||
|
||||
public void subscribe(TaskDO taskDO, Consumer<TaskDO> syncAction) {
|
||||
SpecialSyncEvent specialSyncEvent = new SpecialSyncEvent();
|
||||
specialSyncEvent.setTaskDO(taskDO);
|
||||
specialSyncEvent.setSyncAction(syncAction);
|
||||
specialSyncEventRegistry.putIfAbsent(taskDO.getTaskId(), specialSyncEvent);
|
||||
}
|
||||
|
||||
public void unsubscribe(TaskDO taskDO) {
|
||||
specialSyncEventRegistry.remove(taskDO.getTaskId());
|
||||
}
|
||||
|
||||
public Collection<SpecialSyncEvent> getAllSpecialSyncEvent() {
|
||||
return specialSyncEventRegistry.values();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.event.listener;
|
||||
|
||||
import com.alibaba.nacossync.extension.event.SpecialSyncEvent;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date: 2019-01-12 22:29
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SpecialSyncEventListener {
|
||||
private final EventBus eventBus;
|
||||
|
||||
public SpecialSyncEventListener(EventBus eventBus) {
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
eventBus.register(this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void listenerSpecialSyncEvent(SpecialSyncEvent specialSyncEvent) {
|
||||
TaskDO taskDO = specialSyncEvent.getTaskDO();
|
||||
Consumer<TaskDO> syncAction = specialSyncEvent.getSyncAction();
|
||||
syncAction.accept(taskDO);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.holder;
|
||||
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date 2018-12-24 22:08
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractServerHolderImpl<T> implements Holder<T> {
|
||||
|
||||
private final Map<String, T> serviceMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@Autowired
|
||||
protected SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
@Override
|
||||
public T get(String clusterId) {
|
||||
|
||||
return serviceMap.computeIfAbsent(clusterId, clusterKey -> {
|
||||
try {
|
||||
log.info("Starting create cluster server, clusterId={}", clusterId);
|
||||
return createServer(clusterId, () -> skyWalkerCacheServices.getClusterConnectKey(clusterId));
|
||||
} catch (Exception e) {
|
||||
log.error(String.format("clusterId=%s, start server failed", clusterId), e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create real cluster client instance
|
||||
*
|
||||
* @param clusterId cluster id
|
||||
* @param serverAddressSupplier server address
|
||||
* @return cluster client instance
|
||||
*/
|
||||
abstract T createServer(String clusterId, Supplier<String> serverAddressSupplier)
|
||||
throws Exception;
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.holder;
|
||||
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date: 2018-12-31 16:26
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ConsulServerHolder extends AbstractServerHolderImpl<ConsulClient> {
|
||||
|
||||
public static final String HTTP = "http://";
|
||||
|
||||
@Override
|
||||
ConsulClient createServer(String clusterId, Supplier<String> serverAddressSupplier) throws Exception {
|
||||
String serverAddress = serverAddressSupplier.get();
|
||||
serverAddress = serverAddress.startsWith(HTTP) ? serverAddress : HTTP + serverAddress;
|
||||
URL url = new URL(serverAddress);
|
||||
return new ConsulClient(url.getHost(), url.getPort());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.extension.holder;
|
||||
|
||||
import com.alibaba.nacossync.extension.eureka.EurekaNamingService;
|
||||
import com.netflix.discovery.shared.resolver.DefaultEndpoint;
|
||||
import com.netflix.discovery.shared.resolver.EurekaEndpoint;
|
||||
import com.netflix.discovery.shared.transport.EurekaHttpClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.netflix.eureka.http.RestTemplateTransportClientFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date: 2018-12-31 16:26
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class EurekaServerHolder extends AbstractServerHolderImpl<EurekaNamingService> {
|
||||
|
||||
private static final String HTTP_PREFIX = "http://";
|
||||
|
||||
private static final String HTTPS_PREFIX = "https://";
|
||||
|
||||
@Override
|
||||
EurekaNamingService createServer(String clusterId, Supplier<String> serverAddressSupplier) throws Exception {
|
||||
RestTemplateTransportClientFactory restTemplateTransportClientFactory = new RestTemplateTransportClientFactory();
|
||||
EurekaEndpoint eurekaEndpoint = new DefaultEndpoint(addHttpPrefix(serverAddressSupplier.get()));
|
||||
EurekaHttpClient eurekaHttpClient = restTemplateTransportClientFactory.newClient(eurekaEndpoint);
|
||||
return new EurekaNamingService(eurekaHttpClient);
|
||||
}
|
||||
|
||||
public String addHttpPrefix(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
if (!input.startsWith(HTTP_PREFIX) && !input.startsWith(HTTPS_PREFIX)) {
|
||||
input = HTTP_PREFIX + input;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.holder;
|
||||
|
||||
/**
|
||||
* Cluster client service
|
||||
* @author paderlol
|
||||
* @date 2018-12-24 21:59
|
||||
*/
|
||||
public interface Holder<T> {
|
||||
|
||||
/**
|
||||
* Through the cluster ID and namespace fetch cluster client service
|
||||
* @param clusterId cluster id
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
T get(String clusterId) throws Exception;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.holder;
|
||||
|
||||
import com.alibaba.nacos.api.PropertyKeyConst;
|
||||
import com.alibaba.nacos.api.naming.NamingFactory;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacossync.dao.ClusterAccessService;
|
||||
import com.alibaba.nacossync.pojo.model.ClusterDO;
|
||||
import com.google.common.base.Joiner;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date: 2018-12-24 21:48
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class NacosServerHolder extends AbstractServerHolderImpl<NamingService> {
|
||||
|
||||
private final ClusterAccessService clusterAccessService;
|
||||
|
||||
public NacosServerHolder(ClusterAccessService clusterAccessService) {
|
||||
this.clusterAccessService = clusterAccessService;
|
||||
}
|
||||
|
||||
@Override
|
||||
NamingService createServer(String clusterId, Supplier<String> serverAddressSupplier)
|
||||
throws Exception {
|
||||
List<String> allClusterConnectKey = skyWalkerCacheServices
|
||||
.getAllClusterConnectKey(clusterId);
|
||||
ClusterDO clusterDO = clusterAccessService.findByClusterId(clusterId);
|
||||
String serverList = Joiner.on(",").join(allClusterConnectKey);
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverList);
|
||||
properties.setProperty(PropertyKeyConst.NAMESPACE, Optional.ofNullable(clusterDO.getNamespace()).orElse(
|
||||
Strings.EMPTY));
|
||||
Optional.ofNullable(clusterDO.getUserName()).ifPresent(value ->
|
||||
properties.setProperty(PropertyKeyConst.USERNAME, value)
|
||||
);
|
||||
|
||||
Optional.ofNullable(clusterDO.getPassword()).ifPresent(value ->
|
||||
properties.setProperty(PropertyKeyConst.PASSWORD, value)
|
||||
);
|
||||
return NamingFactory.createNamingService(properties);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.holder;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.CuratorFrameworkFactory;
|
||||
import org.apache.curator.framework.state.ConnectionState;
|
||||
import org.apache.curator.retry.RetryNTimes;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @date: 2018-12-24 22:07
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ZookeeperServerHolder extends AbstractServerHolderImpl<CuratorFramework> {
|
||||
|
||||
|
||||
@Override
|
||||
CuratorFramework createServer(String clusterId, Supplier<String> serverAddressSupplier) {
|
||||
List<String> allClusterConnectKey = skyWalkerCacheServices
|
||||
.getAllClusterConnectKey(clusterId);
|
||||
String serverList = Joiner.on(",").join(allClusterConnectKey);
|
||||
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
|
||||
.connectString(serverList)
|
||||
.retryPolicy(new RetryNTimes(1, 3000))
|
||||
.connectionTimeoutMs(5000);
|
||||
|
||||
CuratorFramework client = builder.build();
|
||||
client.getConnectionStateListenable().addListener((clientInstance, state) -> {
|
||||
if (state == ConnectionState.LOST) {
|
||||
log.error("zk address: {} client state LOST",serverList);
|
||||
} else if (state == ConnectionState.CONNECTED) {
|
||||
log.info("zk address: {} client state CONNECTED",serverList);
|
||||
} else if (state == ConnectionState.RECONNECTED) {
|
||||
log.info("zk address: {} client state RECONNECTED",serverList);
|
||||
}
|
||||
});
|
||||
client.start();
|
||||
return client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacos.api.naming.listener.EventListener;
|
||||
import com.alibaba.nacos.api.naming.listener.NamingEvent;
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||
import com.alibaba.nacossync.constant.MetricsStatisticsType;
|
||||
import com.alibaba.nacossync.extension.SyncService;
|
||||
import com.alibaba.nacossync.extension.holder.NacosServerHolder;
|
||||
import com.alibaba.nacossync.monitor.MetricsManager;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.BatchTaskExecutor;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.alibaba.nacossync.util.NacosUtils.getGroupNameOrDefault;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractNacosSync implements SyncService {
|
||||
|
||||
private final Map<String, EventListener> listenerMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, Set<String>> sourceInstanceSnapshot = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, Integer> syncTaskTap = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<String, TaskDO> allSyncTaskMap = new ConcurrentHashMap<>();
|
||||
private ScheduledExecutorService executorService;
|
||||
@Autowired
|
||||
private MetricsManager metricsManager;
|
||||
|
||||
@Getter
|
||||
@Autowired
|
||||
private NacosServerHolder nacosServerHolder;
|
||||
|
||||
|
||||
/**
|
||||
* Due to network issues or other reasons, the Nacos Sync synchronization tasks may fail,
|
||||
* resulting in the target cluster's registry missing synchronized instances.
|
||||
* To prevent the target cluster's registry from missing synchronized instances for an extended period,
|
||||
* a fallback worker thread is started every 5 minutes to execute all synchronization tasks.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void afterPropertiesSet() {
|
||||
initializeExecutorService();
|
||||
scheduleSyncTasks();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (executorService != null && !executorService.isShutdown()) {
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeExecutorService() {
|
||||
executorService = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setName("com.alibaba.nacossync.basic.synctask");
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleSyncTasks() {
|
||||
executorService.scheduleWithFixedDelay(this::executeSyncTasks, 0, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void executeSyncTasks() {
|
||||
if (allSyncTaskMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<TaskDO> taskCollections = allSyncTaskMap.values();
|
||||
List<TaskDO> taskDOList = new ArrayList<>(taskCollections);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(taskDOList)) {
|
||||
BatchTaskExecutor.batchOperation(taskDOList, this::executeTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeTask(TaskDO task) {
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
String taskId = task.getTaskId();
|
||||
try {
|
||||
NamingService sourceNamingService = nacosServerHolder.get(task.getSourceClusterId());
|
||||
doSync(taskId, task, sourceNamingService);
|
||||
} catch (NacosException e) {
|
||||
log.error("sync task from nacos to nacos failed, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during sync task, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
} finally {
|
||||
stopwatch.stop();
|
||||
log.debug("Task execution time for taskId {}: {} ms", taskId, stopwatch.elapsed(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(TaskDO taskDO) {
|
||||
String taskId = taskDO.getTaskId();
|
||||
try {
|
||||
|
||||
NamingService sourceNamingService = nacosServerHolder.get(taskDO.getSourceClusterId());
|
||||
//移除订阅
|
||||
EventListener listener = listenerMap.remove(taskId);
|
||||
if (listener!= null) {
|
||||
sourceNamingService.unsubscribe(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()), listener);
|
||||
}
|
||||
sourceNamingService.unsubscribe(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
listenerMap.remove(taskId));
|
||||
sourceInstanceSnapshot.remove(taskId);
|
||||
allSyncTaskMap.remove(taskId);
|
||||
|
||||
// 删除目标集群中同步的实例列表
|
||||
deregisterInstance(taskDO);
|
||||
}catch (NacosException e) {
|
||||
log.error("Delete task from nacos to specify destination was failed, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during sync task, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(TaskDO taskDO, Integer index) {
|
||||
String taskId = taskDO.getTaskId();
|
||||
try {
|
||||
NamingService sourceNamingService = nacosServerHolder.get(taskDO.getSourceClusterId());
|
||||
allSyncTaskMap.put(taskId, taskDO);
|
||||
//防止暂停同步任务后,重新同步/或删除任务以后新建任务不会再接收到新的事件导致不能同步,所以每次订阅事件之前,先全量同步一次任务
|
||||
doSync(taskId, taskDO, sourceNamingService);
|
||||
this.listenerMap.putIfAbsent(taskId, event -> {
|
||||
if (event instanceof NamingEvent) {
|
||||
try {
|
||||
doSync(taskId, taskDO, sourceNamingService);
|
||||
} catch (Exception e) {
|
||||
log.error("event process fail, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
sourceNamingService.subscribe(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
listenerMap.get(taskId));
|
||||
}catch (NacosException e) {
|
||||
log.error("Nacos sync task process fail, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during sync task, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doSync(String taskId, TaskDO taskDO, NamingService sourceNamingService) throws Exception {
|
||||
if (syncTaskTap.putIfAbsent(taskId, 1) != null) {
|
||||
log.info("任务Id:{}上一个同步任务尚未结束", taskId);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 直接从本地保存的serviceInfoMap中取订阅的服务实例
|
||||
List<Instance> sourceInstances = sourceNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), new ArrayList<>(), true);
|
||||
// 先删除不存在的
|
||||
this.removeInvalidInstance(taskDO, sourceInstances);
|
||||
|
||||
// 同步实例
|
||||
this.syncNewInstance(taskDO, sourceInstances);
|
||||
} finally {
|
||||
syncTaskTap.remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
private void syncNewInstance(TaskDO taskDO, List<Instance> sourceInstances) throws NacosException {
|
||||
Set<String> latestSyncInstance = new TreeSet<>();
|
||||
//再次添加新实例
|
||||
String taskId = taskDO.getTaskId();
|
||||
Set<String> instanceKeys = sourceInstanceSnapshot.get(taskId);
|
||||
for (Instance instance : sourceInstances) {
|
||||
if (needSync(instance.getMetadata())) {
|
||||
String instanceKey = composeInstanceKey(instance.getIp(), instance.getPort());
|
||||
if (CollectionUtils.isEmpty(instanceKeys) || !instanceKeys.contains(instanceKey)) {
|
||||
register(taskDO, instance);
|
||||
}
|
||||
latestSyncInstance.add(instanceKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(latestSyncInstance)) {
|
||||
log.info("任务Id:{},已同步实例个数:{}", taskId, latestSyncInstance.size());
|
||||
sourceInstanceSnapshot.put(taskId, latestSyncInstance);
|
||||
} else {
|
||||
// latestSyncInstance为空表示源集群中需要同步的所有实例(即非nacos-sync同步过来的实例)已经下线,清除本地持有快照
|
||||
sourceInstanceSnapshot.remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void removeInvalidInstance(TaskDO taskDO, List<Instance> sourceInstances) throws Exception {
|
||||
String taskId = taskDO.getTaskId();
|
||||
if (this.sourceInstanceSnapshot.containsKey(taskId)) {
|
||||
Set<String> oldInstanceKeys = this.sourceInstanceSnapshot.get(taskId);
|
||||
Set<String> newInstanceKeys = sourceInstances.stream()
|
||||
.map(instance -> composeInstanceKey(instance.getIp(), instance.getPort()))
|
||||
.collect(Collectors.toSet());
|
||||
oldInstanceKeys.removeAll(newInstanceKeys);
|
||||
if (CollectionUtils.isNotEmpty(oldInstanceKeys)) {
|
||||
log.info("任务Id:{},移除无效同步实例:{}", taskId, oldInstanceKeys);
|
||||
removeInvalidInstance(taskDO, oldInstanceKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needDelete(Map<String, String> destMetaData, TaskDO taskDO) {
|
||||
return SyncService.super.needDelete(destMetaData, taskDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needSync(Map<String, String> sourceMetaData) {
|
||||
return SyncService.super.needSync(sourceMetaData);
|
||||
}
|
||||
|
||||
public abstract String composeInstanceKey(String ip, int port);
|
||||
|
||||
public abstract void register(TaskDO taskDO, Instance instance);
|
||||
|
||||
public abstract void deregisterInstance(TaskDO taskDO) throws Exception;
|
||||
|
||||
public abstract void removeInvalidInstance(TaskDO taskDO, Set<String> invalidInstanceKeys) throws Exception;
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.constant.MetricsStatisticsType;
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.extension.SyncService;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.extension.event.SpecialSyncEventBus;
|
||||
import com.alibaba.nacossync.extension.holder.ConsulServerHolder;
|
||||
import com.alibaba.nacossync.extension.holder.NacosServerHolder;
|
||||
import com.alibaba.nacossync.monitor.MetricsManager;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.ConsulUtils;
|
||||
import com.alibaba.nacossync.util.NacosUtils;
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
import com.ecwid.consul.v1.QueryParams;
|
||||
import com.ecwid.consul.v1.Response;
|
||||
import com.ecwid.consul.v1.health.model.HealthService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Consul 同步 Nacos
|
||||
*
|
||||
* @author paderlol
|
||||
* @date: 2018-12-31 16:25
|
||||
*/
|
||||
@Slf4j
|
||||
@NacosSyncService(sourceCluster = ClusterTypeEnum.CONSUL, destinationCluster = ClusterTypeEnum.NACOS)
|
||||
public class ConsulSyncToNacosServiceImpl implements SyncService {
|
||||
|
||||
private final MetricsManager metricsManager;
|
||||
|
||||
private final ConsulServerHolder consulServerHolder;
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
private final NacosServerHolder nacosServerHolder;
|
||||
|
||||
private final SpecialSyncEventBus specialSyncEventBus;
|
||||
|
||||
|
||||
public ConsulSyncToNacosServiceImpl(ConsulServerHolder consulServerHolder,
|
||||
SkyWalkerCacheServices skyWalkerCacheServices, NacosServerHolder nacosServerHolder,
|
||||
SpecialSyncEventBus specialSyncEventBus, MetricsManager metricsManager) {
|
||||
this.consulServerHolder = consulServerHolder;
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
this.nacosServerHolder = nacosServerHolder;
|
||||
this.specialSyncEventBus = specialSyncEventBus;
|
||||
this.metricsManager = metricsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(TaskDO taskDO) {
|
||||
|
||||
try {
|
||||
specialSyncEventBus.unsubscribe(taskDO);
|
||||
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
List<Instance> allInstances = destNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()));
|
||||
for (Instance instance : allInstances) {
|
||||
if (needDelete(instance.getMetadata(), taskDO)) {
|
||||
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()), instance.getIp(), instance.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("delete task from consul to nacos was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.DELETE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(TaskDO taskDO, Integer index) {
|
||||
try {
|
||||
ConsulClient consulClient = consulServerHolder.get(taskDO.getSourceClusterId());
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
Response<List<HealthService>> response =
|
||||
consulClient.getHealthServices(taskDO.getServiceName(), true, QueryParams.DEFAULT);
|
||||
List<HealthService> healthServiceList = response.getValue();
|
||||
Set<String> instanceKeys = new HashSet<>();
|
||||
overrideAllInstance(taskDO, destNamingService, healthServiceList, instanceKeys);
|
||||
cleanAllOldInstance(taskDO, destNamingService, instanceKeys);
|
||||
specialSyncEventBus.subscribe(taskDO, t->sync(t, index));
|
||||
} catch (Exception e) {
|
||||
log.error("Sync task from consul to nacos was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void cleanAllOldInstance(TaskDO taskDO, NamingService destNamingService, Set<String> instanceKeys)
|
||||
throws NacosException {
|
||||
List<Instance> allInstances = destNamingService.getAllInstances(taskDO.getServiceName());
|
||||
for (Instance instance : allInstances) {
|
||||
if (needDelete(instance.getMetadata(), taskDO)
|
||||
&& !instanceKeys.contains(composeInstanceKey(instance.getIp(), instance.getPort()))) {
|
||||
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()), instance.getIp(), instance.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void overrideAllInstance(TaskDO taskDO, NamingService destNamingService,
|
||||
List<HealthService> healthServiceList, Set<String> instanceKeys) throws NacosException {
|
||||
for (HealthService healthService : healthServiceList) {
|
||||
if (needSync(ConsulUtils.transferMetadata(healthService.getService().getTags()))) {
|
||||
destNamingService.registerInstance(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
buildSyncInstance(healthService, taskDO));
|
||||
instanceKeys.add(composeInstanceKey(healthService.getService().getAddress(),
|
||||
healthService.getService().getPort()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Instance buildSyncInstance(HealthService instance, TaskDO taskDO) {
|
||||
Instance temp = new Instance();
|
||||
temp.setIp(instance.getService().getAddress());
|
||||
temp.setPort(instance.getService().getPort());
|
||||
Map<String, String> metaData = new HashMap<>(ConsulUtils.transferMetadata(instance.getService().getTags()));
|
||||
metaData.put(SkyWalkerConstants.DEST_CLUSTER_ID_KEY, taskDO.getDestClusterId());
|
||||
metaData.put(SkyWalkerConstants.SYNC_SOURCE_KEY,
|
||||
skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId()).getCode());
|
||||
metaData.put(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY, taskDO.getSourceClusterId());
|
||||
temp.setMetadata(metaData);
|
||||
return temp;
|
||||
}
|
||||
|
||||
private String composeInstanceKey(String ip, int port) {
|
||||
return ip + ":" + port;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.constant.MetricsStatisticsType;
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.extension.SyncService;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.extension.eureka.EurekaNamingService;
|
||||
import com.alibaba.nacossync.extension.event.SpecialSyncEventBus;
|
||||
import com.alibaba.nacossync.extension.holder.EurekaServerHolder;
|
||||
import com.alibaba.nacossync.extension.holder.NacosServerHolder;
|
||||
import com.alibaba.nacossync.monitor.MetricsManager;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.NacosUtils;
|
||||
import com.netflix.appinfo.InstanceInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* eureka
|
||||
*
|
||||
* @author paderlol
|
||||
* @date: 2018-12-31 16:25
|
||||
*/
|
||||
@Slf4j
|
||||
@NacosSyncService(sourceCluster = ClusterTypeEnum.EUREKA, destinationCluster = ClusterTypeEnum.NACOS)
|
||||
public class EurekaSyncToNacosServiceImpl implements SyncService {
|
||||
|
||||
private final MetricsManager metricsManager;
|
||||
|
||||
private final EurekaServerHolder eurekaServerHolder;
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
private final NacosServerHolder nacosServerHolder;
|
||||
|
||||
private final SpecialSyncEventBus specialSyncEventBus;
|
||||
|
||||
@Autowired
|
||||
public EurekaSyncToNacosServiceImpl(EurekaServerHolder eurekaServerHolder,
|
||||
SkyWalkerCacheServices skyWalkerCacheServices, NacosServerHolder nacosServerHolder,
|
||||
SpecialSyncEventBus specialSyncEventBus, MetricsManager metricsManager) {
|
||||
this.eurekaServerHolder = eurekaServerHolder;
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
this.nacosServerHolder = nacosServerHolder;
|
||||
this.specialSyncEventBus = specialSyncEventBus;
|
||||
this.metricsManager = metricsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(TaskDO taskDO) {
|
||||
|
||||
try {
|
||||
specialSyncEventBus.unsubscribe(taskDO);
|
||||
|
||||
EurekaNamingService eurekaNamingService = eurekaServerHolder.get(taskDO.getSourceClusterId());
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
|
||||
List<InstanceInfo> eurekaInstances = eurekaNamingService.getApplications(taskDO.getServiceName());
|
||||
deleteAllInstanceFromEureka(taskDO, destNamingService, eurekaInstances);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("delete a task from eureka to nacos was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.DELETE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(TaskDO taskDO,Integer index) {
|
||||
try {
|
||||
|
||||
EurekaNamingService eurekaNamingService = eurekaServerHolder.get(taskDO.getSourceClusterId());
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
|
||||
List<InstanceInfo> eurekaInstances = eurekaNamingService.getApplications(taskDO.getServiceName());
|
||||
List<Instance> nacosInstances = destNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()));
|
||||
|
||||
if (CollectionUtils.isEmpty(eurekaInstances)) {
|
||||
// Clear all instance from Nacos
|
||||
deleteAllInstance(taskDO, destNamingService, nacosInstances);
|
||||
} else {
|
||||
if (!CollectionUtils.isEmpty(nacosInstances)) {
|
||||
// Remove invalid instance from Nacos
|
||||
removeInvalidInstance(taskDO, destNamingService, eurekaInstances, nacosInstances);
|
||||
}
|
||||
addValidInstance(taskDO, destNamingService, eurekaInstances);
|
||||
}
|
||||
specialSyncEventBus.subscribe(taskDO, t->sync(t, index));
|
||||
} catch (Exception e) {
|
||||
log.error("sync task from eureka to nacos was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addValidInstance(TaskDO taskDO, NamingService destNamingService, List<InstanceInfo> eurekaInstances)
|
||||
throws NacosException {
|
||||
for (InstanceInfo instance : eurekaInstances) {
|
||||
if (needSync(instance.getMetadata())) {
|
||||
log.info("Add service instance from Eureka, serviceName={}, Ip={}, port={}",
|
||||
instance.getAppName(), instance.getIPAddr(), instance.getPort());
|
||||
destNamingService.registerInstance(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()), buildSyncInstance(instance,
|
||||
taskDO));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteAllInstanceFromEureka(TaskDO taskDO, NamingService destNamingService,
|
||||
List<InstanceInfo> eurekaInstances)
|
||||
throws NacosException {
|
||||
if (CollectionUtils.isEmpty(eurekaInstances)) {
|
||||
return;
|
||||
}
|
||||
for (InstanceInfo instance : eurekaInstances) {
|
||||
if (needSync(instance.getMetadata())) {
|
||||
log.info("Delete service instance from Eureka, serviceName={}, Ip={}, port={}",
|
||||
instance.getAppName(), instance.getIPAddr(), instance.getPort());
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()), buildSyncInstance(instance, taskDO));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeInvalidInstance(TaskDO taskDO, NamingService destNamingService,
|
||||
List<InstanceInfo> eurekaInstances, List<Instance> nacosInstances) throws NacosException {
|
||||
for (Instance instance : nacosInstances) {
|
||||
if (!isExistInEurekaInstance(eurekaInstances, instance) && needDelete(instance.getMetadata(), taskDO)) {
|
||||
log.info("Remove invalid service instance from Nacos, serviceName={}, Ip={}, port={}",
|
||||
instance.getServiceName(), instance.getIp(), instance.getPort());
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()), instance.getIp(), instance.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExistInEurekaInstance(List<InstanceInfo> eurekaInstances, Instance nacosInstance) {
|
||||
|
||||
return eurekaInstances.stream().anyMatch(instance -> instance.getIPAddr().equals(nacosInstance.getIp())
|
||||
&& instance.getPort() == nacosInstance.getPort());
|
||||
}
|
||||
|
||||
private void deleteAllInstance(TaskDO taskDO, NamingService destNamingService, List<Instance> allInstances)
|
||||
throws NacosException {
|
||||
for (Instance instance : allInstances) {
|
||||
if (needDelete(instance.getMetadata(), taskDO)) {
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(),
|
||||
NacosUtils.getGroupNameOrDefault(taskDO.getGroupName()), instance);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Instance buildSyncInstance(InstanceInfo instance, TaskDO taskDO) {
|
||||
Instance temp = new Instance();
|
||||
temp.setIp(instance.getIPAddr());
|
||||
temp.setPort(instance.getPort());
|
||||
temp.setServiceName(instance.getAppName());
|
||||
temp.setHealthy(true);
|
||||
|
||||
Map<String, String> metaData = new HashMap<>(instance.getMetadata());
|
||||
metaData.put(SkyWalkerConstants.DEST_CLUSTER_ID_KEY, taskDO.getDestClusterId());
|
||||
metaData.put(SkyWalkerConstants.SYNC_SOURCE_KEY,
|
||||
skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId()).getCode());
|
||||
metaData.put(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY, taskDO.getSourceClusterId());
|
||||
temp.setMetadata(metaData);
|
||||
return temp;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.extension.holder.ConsulServerHolder;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.ConsulUtils;
|
||||
import com.ecwid.consul.v1.ConsulClient;
|
||||
import com.ecwid.consul.v1.QueryParams;
|
||||
import com.ecwid.consul.v1.Response;
|
||||
import com.ecwid.consul.v1.agent.model.NewService;
|
||||
import com.ecwid.consul.v1.health.model.HealthService;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author zhanglong
|
||||
*/
|
||||
@Slf4j
|
||||
@NacosSyncService(sourceCluster = ClusterTypeEnum.NACOS, destinationCluster = ClusterTypeEnum.CONSUL)
|
||||
public class NacosSyncToConsulServiceImpl extends AbstractNacosSync {
|
||||
|
||||
|
||||
private static final String DELIMITER = "=";
|
||||
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
private final ConsulServerHolder consulServerHolder;
|
||||
|
||||
public NacosSyncToConsulServiceImpl(SkyWalkerCacheServices skyWalkerCacheServices,
|
||||
ConsulServerHolder consulServerHolder) {
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
this.consulServerHolder = consulServerHolder;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String composeInstanceKey(String ip, int port) {
|
||||
return String.join(":", ip, String.valueOf(port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(TaskDO taskDO, Instance instance) {
|
||||
ConsulClient consulClient = consulServerHolder.get(taskDO.getDestClusterId());
|
||||
consulClient.agentServiceRegister(buildSyncInstance(instance, taskDO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deregisterInstance(TaskDO taskDO) throws Exception {
|
||||
ConsulClient consulClient = consulServerHolder.get(taskDO.getDestClusterId());
|
||||
Response<List<HealthService>> serviceResponse = consulClient.getHealthServices(taskDO.getServiceName(), true,
|
||||
QueryParams.DEFAULT);
|
||||
List<HealthService> healthServices = serviceResponse.getValue();
|
||||
for (HealthService healthService : healthServices) {
|
||||
|
||||
if (needDelete(ConsulUtils.transferMetadata(healthService.getService().getTags()), taskDO)) {
|
||||
consulClient.agentServiceDeregister(
|
||||
URLEncoder.encode(healthService.getService().getId(), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeInvalidInstance(TaskDO taskDO, Set<String> invalidInstanceKeys)
|
||||
throws UnsupportedEncodingException {
|
||||
ConsulClient consulClient = consulServerHolder.get(taskDO.getDestClusterId());
|
||||
Response<List<HealthService>> serviceResponse = consulClient.getHealthServices(taskDO.getServiceName(), true,
|
||||
QueryParams.DEFAULT);
|
||||
List<HealthService> healthServices = serviceResponse.getValue();
|
||||
for (HealthService healthService : healthServices) {
|
||||
|
||||
if (needDelete(ConsulUtils.transferMetadata(healthService.getService().getTags()), taskDO)
|
||||
&& !invalidInstanceKeys.contains(composeInstanceKey(healthService.getService().getAddress(),
|
||||
healthService.getService().getPort()))) {
|
||||
consulClient.agentServiceDeregister(
|
||||
URLEncoder.encode(healthService.getService().getId(), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NewService buildSyncInstance(Instance instance, TaskDO taskDO) {
|
||||
NewService newService = new NewService();
|
||||
newService.setAddress(instance.getIp());
|
||||
newService.setPort(instance.getPort());
|
||||
newService.setName(taskDO.getServiceName());
|
||||
newService.setId(instance.getInstanceId());
|
||||
List<String> tags = Lists.newArrayList();
|
||||
tags.addAll(instance.getMetadata().entrySet().stream()
|
||||
.map(entry -> String.join(DELIMITER, entry.getKey(), entry.getValue())).collect(Collectors.toList()));
|
||||
tags.add(String.join(DELIMITER, SkyWalkerConstants.DEST_CLUSTER_ID_KEY, taskDO.getDestClusterId()));
|
||||
tags.add(String.join(DELIMITER, SkyWalkerConstants.SYNC_SOURCE_KEY,
|
||||
skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId()).getCode()));
|
||||
tags.add(String.join(DELIMITER, SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY, taskDO.getSourceClusterId()));
|
||||
newService.setTags(tags);
|
||||
return newService;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.extension.eureka.EurekaNamingService;
|
||||
import com.alibaba.nacossync.extension.holder.EurekaServerHolder;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.netflix.appinfo.DataCenterInfo;
|
||||
import com.netflix.appinfo.InstanceInfo;
|
||||
import com.netflix.appinfo.LeaseInfo;
|
||||
import com.netflix.appinfo.MyDataCenterInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author zhanglong
|
||||
*/
|
||||
@Slf4j
|
||||
@NacosSyncService(sourceCluster = ClusterTypeEnum.NACOS, destinationCluster = ClusterTypeEnum.EUREKA)
|
||||
public class NacosSyncToEurekaServiceImpl extends AbstractNacosSync {
|
||||
|
||||
|
||||
private final EurekaServerHolder eurekaServerHolder;
|
||||
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
public NacosSyncToEurekaServiceImpl(EurekaServerHolder eurekaServerHolder,
|
||||
SkyWalkerCacheServices skyWalkerCacheServices) {
|
||||
this.eurekaServerHolder = eurekaServerHolder;
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String composeInstanceKey(String ip, int port) {
|
||||
return ip + ":" + port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(TaskDO taskDO, Instance instance) {
|
||||
EurekaNamingService destNamingService = eurekaServerHolder.get(taskDO.getDestClusterId());
|
||||
destNamingService.registerInstance(buildSyncInstance(instance, taskDO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deregisterInstance(TaskDO taskDO) {
|
||||
EurekaNamingService destNamingService = eurekaServerHolder.get(taskDO.getDestClusterId());
|
||||
List<InstanceInfo> allInstances = destNamingService.getApplications(taskDO.getServiceName());
|
||||
if (allInstances != null) {
|
||||
for (InstanceInfo instance : allInstances) {
|
||||
if (needDelete(instance.getMetadata(), taskDO)) {
|
||||
destNamingService.deregisterInstance(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeInvalidInstance(TaskDO taskDO, Set<String> invalidInstanceKeys) {
|
||||
EurekaNamingService destNamingService = eurekaServerHolder.get(taskDO.getDestClusterId());
|
||||
List<InstanceInfo> allInstances = destNamingService.getApplications(taskDO.getServiceName());
|
||||
if (CollectionUtils.isNotEmpty(allInstances)) {
|
||||
for (InstanceInfo instance : allInstances) {
|
||||
if (needDelete(instance.getMetadata(), taskDO) && invalidInstanceKeys.contains(
|
||||
composeInstanceKey(instance.getIPAddr(), instance.getPort()))) {
|
||||
destNamingService.deregisterInstance(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private InstanceInfo buildSyncInstance(Instance instance, TaskDO taskDO) {
|
||||
DataCenterInfo dataCenterInfo = new MyDataCenterInfo(DataCenterInfo.Name.MyOwn);
|
||||
final Map<String, String> instanceMetadata = instance.getMetadata();
|
||||
HashMap<String, String> metadata = new HashMap<>(16);
|
||||
metadata.put(SkyWalkerConstants.DEST_CLUSTER_ID_KEY, taskDO.getDestClusterId());
|
||||
metadata.put(SkyWalkerConstants.SYNC_SOURCE_KEY,
|
||||
skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId()).getCode());
|
||||
metadata.put(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY, taskDO.getSourceClusterId());
|
||||
metadata.putAll(instanceMetadata);
|
||||
String homePageUrl = obtainHomePageUrl(instance, instanceMetadata);
|
||||
String serviceName = taskDO.getServiceName();
|
||||
|
||||
return new InstanceInfo(instance.getIp() + ":" + serviceName + ":" + instance.getPort(), serviceName, null,
|
||||
instance.getIp(), null, new InstanceInfo.PortWrapper(true, instance.getPort()), null,
|
||||
homePageUrl + "/actuator/env", homePageUrl + "/actuator/info", homePageUrl + "/actuator/health", null,
|
||||
serviceName, serviceName, 1, dataCenterInfo, instance.getIp(), InstanceInfo.InstanceStatus.UP,
|
||||
InstanceInfo.InstanceStatus.UNKNOWN, null, new LeaseInfo(30, 90, 0L, 0L, 0L, 0L, 0L), false, metadata,
|
||||
System.currentTimeMillis(), System.currentTimeMillis(), null, null);
|
||||
}
|
||||
|
||||
private String obtainHomePageUrl(Instance instance, Map<String, String> instanceMetadata) {
|
||||
final String managementContextPath = obtainManagementContextPath(instanceMetadata);
|
||||
final String managementPort = instanceMetadata.getOrDefault(SkyWalkerConstants.MANAGEMENT_PORT_KEY,
|
||||
String.valueOf(instance.getPort()));
|
||||
return String.format("http://%s:%s%s", instance.getIp(), managementPort, managementContextPath);
|
||||
}
|
||||
|
||||
private String obtainManagementContextPath(Map<String, String> instanceMetadata) {
|
||||
final String path = instanceMetadata.getOrDefault(SkyWalkerConstants.MANAGEMENT_CONTEXT_PATH_KEY, "");
|
||||
if (path.endsWith("/")) {
|
||||
return path.substring(0, path.length() - 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacos.api.naming.listener.EventListener;
|
||||
import com.alibaba.nacos.api.naming.listener.NamingEvent;
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacos.common.utils.CollectionUtils;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.constant.MetricsStatisticsType;
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.dao.ClusterAccessService;
|
||||
import com.alibaba.nacossync.extension.SyncService;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.extension.holder.NacosServerHolder;
|
||||
import com.alibaba.nacossync.monitor.MetricsManager;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.BatchTaskExecutor;
|
||||
import com.alibaba.nacossync.util.StringUtils;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.alibaba.nacossync.constant.SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY;
|
||||
import static com.alibaba.nacossync.util.NacosUtils.getGroupNameOrDefault;
|
||||
|
||||
/**
|
||||
* @author yangyshdan
|
||||
* @version $Id: ConfigServerSyncManagerService.java, v 0.1 2018-11-12 下午5:17 NacosSync Exp $$
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@NacosSyncService(sourceCluster = ClusterTypeEnum.NACOS, destinationCluster = ClusterTypeEnum.NACOS)
|
||||
public class NacosSyncToNacosServiceImpl implements SyncService, InitializingBean, DisposableBean {
|
||||
|
||||
private final Map<String, EventListener> listenerMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, Integer> syncTaskTap = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<String, TaskDO> allSyncTaskMap = new ConcurrentHashMap<>();
|
||||
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
private final MetricsManager metricsManager;
|
||||
|
||||
private final NacosServerHolder nacosServerHolder;
|
||||
|
||||
private final ClusterAccessService clusterAccessService;
|
||||
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
public NacosSyncToNacosServiceImpl(MetricsManager metricsManager, NacosServerHolder nacosServerHolder,
|
||||
ClusterAccessService clusterAccessService, SkyWalkerCacheServices skyWalkerCacheServices) {
|
||||
this.metricsManager = metricsManager;
|
||||
this.nacosServerHolder = nacosServerHolder;
|
||||
this.clusterAccessService = clusterAccessService;
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to network issues or other reasons, the Nacos Sync synchronization tasks may fail,
|
||||
* resulting in the target cluster's registry missing synchronized instances.
|
||||
* To prevent the target cluster's registry from missing synchronized instances for an extended period,
|
||||
* a fallback worker thread is started every 5 minutes to execute all synchronization tasks.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
initializeExecutorService();
|
||||
scheduleSyncTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (executorService != null && !executorService.isShutdown()) {
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeExecutorService() {
|
||||
executorService = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setName("com.alibaba.nacossync.basic.synctask");
|
||||
return t;
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleSyncTasks() {
|
||||
executorService.scheduleWithFixedDelay(this::executeSyncTasks, 0, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void executeSyncTasks() {
|
||||
if (allSyncTaskMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<TaskDO> taskCollections = allSyncTaskMap.values();
|
||||
List<TaskDO> taskDOList = new ArrayList<>(taskCollections);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(taskDOList)) {
|
||||
BatchTaskExecutor.batchOperation(taskDOList, this::executeTask);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeTask(TaskDO task) {
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
String taskId = task.getTaskId();
|
||||
try {
|
||||
NamingService sourceNamingService = nacosServerHolder.get(task.getSourceClusterId());
|
||||
NamingService destNamingService = nacosServerHolder.get(task.getDestClusterId());
|
||||
doSync(taskId, task, sourceNamingService, destNamingService);
|
||||
} catch (NacosException e) {
|
||||
log.error("sync task from nacos to nacos failed, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during sync task, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
} finally {
|
||||
stopwatch.stop();
|
||||
log.debug("Task execution time for taskId {}: {} ms", taskId, stopwatch.elapsed(TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(TaskDO taskDO) {
|
||||
try {
|
||||
NamingService sourceNamingService = nacosServerHolder.get(taskDO.getSourceClusterId());
|
||||
int level = clusterAccessService.findClusterLevel(taskDO.getSourceClusterId());
|
||||
String taskId = taskDO.getTaskId();
|
||||
|
||||
//Handle individual service
|
||||
if (StringUtils.isEmpty(taskId)) {
|
||||
log.warn("taskId is null data synchronization is not currently performed.{}", taskId);
|
||||
return false;
|
||||
}
|
||||
EventListener listener = listenerMap.remove(taskId);
|
||||
if (listener!= null) {
|
||||
sourceNamingService.unsubscribe(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()), listener);
|
||||
}
|
||||
List<Instance> sourceInstances = sourceNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), new ArrayList<>(), false);
|
||||
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
for (Instance instance : sourceInstances) {
|
||||
if (needSync(instance.getMetadata(), level, taskDO.getDestClusterId())) {
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), instance);
|
||||
}
|
||||
}
|
||||
// Remove all tasks that need to be synchronized.
|
||||
allSyncTaskMap.remove(taskId);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("delete task from nacos to nacos was failed, operationalId:{}", taskDO.getOperationId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.DELETE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(TaskDO taskDO, Integer index) {
|
||||
log.info("Thread {} started synchronization at {}", Thread.currentThread().getId(), System.currentTimeMillis());
|
||||
String taskId = taskDO.getTaskId();
|
||||
try {
|
||||
NamingService sourceNamingService = nacosServerHolder.get(taskDO.getSourceClusterId());
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
allSyncTaskMap.put(taskId, taskDO);
|
||||
// To prevent issues where tasks paused for synchronization, newly created tasks after deletion,
|
||||
// or resynchronization tasks do not receive new events and hence cannot synchronize,
|
||||
// perform a full synchronization of tasks before subscribing to events each time.
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
doSync(taskId, taskDO, sourceNamingService, destNamingService);
|
||||
log.debug("Time taken to synchronize a service registration: {} ms",
|
||||
stopwatch.elapsed(TimeUnit.MILLISECONDS));
|
||||
this.listenerMap.putIfAbsent(taskId, event -> {
|
||||
if (event instanceof NamingEvent) {
|
||||
NamingEvent namingEvent = (NamingEvent) event;
|
||||
log.info("Detected changes in service {} information, taskId: {}, number of instances: {}, initiating synchronization",
|
||||
namingEvent.getServiceName(), taskId,
|
||||
namingEvent.getInstances() == null ? null : namingEvent.getInstances().size());
|
||||
try {
|
||||
doSync(taskId, taskDO, sourceNamingService, destNamingService);
|
||||
log.info("Detected synchronization end for service {}", namingEvent.getServiceName());
|
||||
} catch (Exception e) {
|
||||
log.error("event process fail, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
sourceNamingService.subscribe(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
listenerMap.get(taskId));
|
||||
} catch (Exception e) {
|
||||
log.error("sync task from nacos to nacos was failed, taskId:{}", taskId, e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void doSync(String taskId, TaskDO taskDO, NamingService sourceNamingService,
|
||||
NamingService destNamingService) throws NacosException {
|
||||
if (syncTaskTap.putIfAbsent(taskId, 1) != null) {
|
||||
log.info("Task ID:{} - the previous synchronization task has not finished yet", taskId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
List<Instance> sourceInstances = sourceNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), new ArrayList<>(), true);
|
||||
|
||||
int level = clusterAccessService.findClusterLevel(taskDO.getSourceClusterId());
|
||||
if (CollectionUtils.isNotEmpty(sourceInstances) && sourceInstances.get(0).isEphemeral()) {
|
||||
// Handle batch data synchronization of ephemeral instances, need to get all current service instances.
|
||||
// TODO: When the Client is version 1.x, execute the same synchronization method as persistent instances.
|
||||
handlerPersistenceInstance(taskDO, destNamingService, sourceInstances, level);
|
||||
} else if (CollectionUtils.isEmpty(sourceInstances)) {
|
||||
// If the current source cluster is empty, then directly deregister the instances in the target cluster.
|
||||
log.debug("service {} needs to sync ephemeral instance num is null: serviceName ", taskDO.getServiceName());
|
||||
processDeRegisterInstances(taskDO, destNamingService);
|
||||
} else {
|
||||
// Handle batch data synchronization of persistent instances.
|
||||
handlerPersistenceInstance(taskDO, destNamingService, sourceInstances, level);
|
||||
}
|
||||
} finally {
|
||||
syncTaskTap.remove(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlerPersistenceInstance(TaskDO taskDO, NamingService destNamingService,
|
||||
List<Instance> sourceInstances, int level) throws NacosException {
|
||||
List<Instance> needRegisterInstance = new ArrayList<>();
|
||||
for (Instance instance : sourceInstances) {
|
||||
if (needSync(instance.getMetadata(), level, taskDO.getDestClusterId())) {
|
||||
needRegisterInstance.add(buildSyncInstance(instance, taskDO));
|
||||
}
|
||||
}
|
||||
List<Instance> destAllInstances = destNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), new ArrayList<>(), true);
|
||||
|
||||
// Get the instances that the destination cluster has already synchronized
|
||||
List<Instance> destHasSyncInstances = destAllInstances.stream()
|
||||
.filter(instance -> hasSync(instance, taskDO.getSourceClusterId())).collect(Collectors.toList());
|
||||
|
||||
// The following two conversions are necessary because the Nacos Instance's equals method
|
||||
// is flawed and cannot be used for direct comparison.
|
||||
// The reason is that Instance's equals method compares Metadata using the toString method,
|
||||
// and Metadata is of type HashMap, which does not guarantee order in its toString representation.
|
||||
|
||||
// Convert destHasSyncInstances to a Map with the concatenated string as key
|
||||
Map<String, Instance> destInstanceMap = destHasSyncInstances.stream()
|
||||
.collect(Collectors.toMap(NacosSyncToNacosServiceImpl::getInstanceKey, instance -> instance));
|
||||
|
||||
// Convert newInstances to a Map with the concatenated string as key
|
||||
Map<String, Instance> needRegisterMap = needRegisterInstance.stream()
|
||||
.collect(Collectors.toMap(NacosSyncToNacosServiceImpl::getInstanceKey, instance -> instance));
|
||||
|
||||
// Remove instances from newInstanceMap that are present in destInstanceMap
|
||||
List<Instance> newInstances = removeSyncedInstances(destInstanceMap, needRegisterMap);
|
||||
|
||||
// Remove instances from destInstanceMap that are present in newInstanceMap
|
||||
List<Instance> invalidInstances = getInvalidInstances(destInstanceMap, needRegisterMap);
|
||||
|
||||
// Register each instance one by one. Take one instance at a time.
|
||||
for (Instance newInstance : newInstances) {
|
||||
destNamingService.registerInstance(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
newInstance);
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(invalidInstances)) {
|
||||
log.info("taskId: {}, service {} deregistered, number of executions: {}", taskDO.getTaskId(), taskDO.getServiceName(),
|
||||
destHasSyncInstances.size());
|
||||
}
|
||||
for (Instance instance : invalidInstances) {
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
instance);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Instance> getInvalidInstances(Map<String, Instance> destInstanceMap, Map<String, Instance> needRegisterMap) {
|
||||
Map<String, Instance> destClone = new HashMap<>(destInstanceMap);
|
||||
|
||||
// Convert newInstances to a Map with the concatenated string as key
|
||||
Map<String, Instance> needRegisterClone = new HashMap<>(needRegisterMap);
|
||||
|
||||
// Remove instances from newInstanceMap that are present in destInstanceMap
|
||||
destClone.keySet().removeAll(needRegisterClone.keySet());
|
||||
|
||||
return new ArrayList<>(destClone.values());
|
||||
}
|
||||
|
||||
|
||||
public List<Instance> removeSyncedInstances(Map<String, Instance> destInstanceMap, Map<String, Instance> needRegisterMap) {
|
||||
// Convert destHasSyncInstances to a Map with the concatenated string as key
|
||||
|
||||
Map<String, Instance> destClone = new HashMap<>(destInstanceMap);
|
||||
|
||||
// Convert newInstances to a Map with the concatenated string as key
|
||||
Map<String, Instance> needRegisterClone = new HashMap<>(needRegisterMap);
|
||||
|
||||
// Remove instances from newInstanceMap that are present in destInstanceMap
|
||||
needRegisterClone.keySet().removeAll(destClone.keySet());
|
||||
|
||||
return new ArrayList<>(needRegisterClone.values());
|
||||
}
|
||||
|
||||
|
||||
private boolean hasSync(Instance instance, String sourceClusterId) {
|
||||
if (instance.getMetadata() != null) {
|
||||
String sourceClusterKey = instance.getMetadata().get(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY);
|
||||
return sourceClusterKey != null && sourceClusterKey.equals(sourceClusterId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When the number of instances that the source cluster needs to synchronize is 0,
|
||||
* if the target cluster still has instances synchronized with the source cluster,
|
||||
* perform unregistration.
|
||||
*
|
||||
* @param destNamingService Destination cluster naming service
|
||||
* @throws NacosException
|
||||
*/
|
||||
private void processDeRegisterInstances(TaskDO taskDO, NamingService destNamingService) throws NacosException {
|
||||
// If the instances in sourceInstances are empty, it means the instances are offline or do not exist.
|
||||
List<Instance> destInstances = destNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), new ArrayList<>(), false);
|
||||
// If the instances in the target cluster are also empty, then no operation is needed.
|
||||
if (CollectionUtils.isEmpty(destInstances)) {
|
||||
return;
|
||||
}
|
||||
destInstances = filterInstancesForRemoval(destInstances, taskDO.getSourceClusterId());
|
||||
if (CollectionUtils.isNotEmpty(destInstances)) {
|
||||
// Deregister each instance one by one. Take one instance at a time.
|
||||
// Need to handle redo, otherwise, it will be registered again.
|
||||
for (Instance destInstance : destInstances) {
|
||||
destNamingService.deregisterInstance(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), destInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Instance> filterInstancesForRemoval(List<Instance> destInstances, String sourceClusterId) {
|
||||
return destInstances.stream().filter(instance -> !instance.getMetadata().isEmpty())
|
||||
.filter(instance -> needDeregister(instance.getMetadata().get(SOURCE_CLUSTER_ID_KEY), sourceClusterId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
private boolean needDeregister(String destClusterId, String sourceClusterId) {
|
||||
if (!StringUtils.isEmpty(destClusterId)) {
|
||||
return destClusterId.equals(sourceClusterId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean needSync(Map<String, String> sourceMetaData, int level, String destClusterId) {
|
||||
// Regular cluster (default)
|
||||
if (level == 0) {
|
||||
return SyncService.super.needSync(sourceMetaData);
|
||||
}
|
||||
// Central cluster, as long as the instance is not from the target cluster,
|
||||
// it needs to be synchronized (extended functionality)
|
||||
return !destClusterId.equals(sourceMetaData.get(SOURCE_CLUSTER_ID_KEY));
|
||||
}
|
||||
|
||||
|
||||
private Instance buildSyncInstance(Instance instance, TaskDO taskDO) {
|
||||
Instance temp = getInstance(instance);
|
||||
temp.addMetadata(SkyWalkerConstants.DEST_CLUSTER_ID_KEY, taskDO.getDestClusterId());
|
||||
temp.addMetadata(SkyWalkerConstants.SYNC_SOURCE_KEY,
|
||||
skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId()).getCode());
|
||||
temp.addMetadata(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY, taskDO.getSourceClusterId());
|
||||
//The flag is a synchronous instance
|
||||
temp.addMetadata(SkyWalkerConstants.SYNC_INSTANCE_TAG,
|
||||
taskDO.getSourceClusterId() + "@@" + taskDO.getVersion());
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static Instance getInstance(Instance instance) {
|
||||
Instance temp = new Instance();
|
||||
temp.setInstanceId(instance.getInstanceId());
|
||||
temp.setIp(instance.getIp());
|
||||
temp.setPort(instance.getPort());
|
||||
temp.setClusterName(instance.getClusterName());
|
||||
temp.setServiceName(instance.getServiceName());
|
||||
temp.setEnabled(instance.isEnabled());
|
||||
temp.setHealthy(instance.isHealthy());
|
||||
temp.setWeight(instance.getWeight());
|
||||
temp.setEphemeral(instance.isEphemeral());
|
||||
Map<String, String> metaData = new HashMap<>(instance.getMetadata());
|
||||
temp.setMetadata(metaData);
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static String getInstanceKey(Instance instance) {
|
||||
return String.join("|",
|
||||
instance.getIp(),
|
||||
String.valueOf(instance.getPort()),
|
||||
String.valueOf(instance.getWeight()),
|
||||
String.valueOf(instance.isHealthy()),
|
||||
String.valueOf(instance.isEphemeral()),
|
||||
instance.getClusterName(),
|
||||
instance.getServiceName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacos.api.naming.listener.EventListener;
|
||||
import com.alibaba.nacos.api.naming.listener.NamingEvent;
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.constant.MetricsStatisticsType;
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.extension.SyncService;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.extension.holder.NacosServerHolder;
|
||||
import com.alibaba.nacossync.extension.holder.ZookeeperServerHolder;
|
||||
import com.alibaba.nacossync.monitor.MetricsManager;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import com.alibaba.nacossync.util.DubboConstants;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
|
||||
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
|
||||
import org.apache.curator.utils.CloseableUtils;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.alibaba.nacossync.util.NacosUtils.getGroupNameOrDefault;
|
||||
import static com.alibaba.nacossync.util.StringUtils.convertDubboFullPathForZk;
|
||||
import static com.alibaba.nacossync.util.StringUtils.convertDubboProvidersPath;
|
||||
|
||||
/**
|
||||
* Nacos 同步 Zk 数据
|
||||
*
|
||||
* @author paderlol
|
||||
* @date 2019年01月06日, 15:08:06
|
||||
*/
|
||||
@Slf4j
|
||||
@NacosSyncService(sourceCluster = ClusterTypeEnum.NACOS, destinationCluster = ClusterTypeEnum.ZK)
|
||||
public class NacosSyncToZookeeperServiceImpl implements SyncService {
|
||||
|
||||
private final MetricsManager metricsManager;
|
||||
|
||||
/**
|
||||
* @description The Nacos listener map.
|
||||
*/
|
||||
private final Map<String, EventListener> nacosListenerMap = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* instance backup
|
||||
*/
|
||||
private final Map<String, Set<String>> instanceBackupMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* listener cache of zookeeper format: taskId -> PathChildrenCache instance
|
||||
*/
|
||||
private final Map<String, PathChildrenCache> pathChildrenCacheMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* zookeeper path for dubbo providers
|
||||
*/
|
||||
private final Map<String, String> monitorPath = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* @description The Sky walker cache services.
|
||||
*/
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
/**
|
||||
* @description The Nacos server holder.
|
||||
*/
|
||||
private final NacosServerHolder nacosServerHolder;
|
||||
|
||||
private final ZookeeperServerHolder zookeeperServerHolder;
|
||||
|
||||
|
||||
public NacosSyncToZookeeperServiceImpl(SkyWalkerCacheServices skyWalkerCacheServices,
|
||||
NacosServerHolder nacosServerHolder, ZookeeperServerHolder zookeeperServerHolder, MetricsManager metricsManager) {
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
this.nacosServerHolder = nacosServerHolder;
|
||||
this.zookeeperServerHolder = zookeeperServerHolder;
|
||||
this.metricsManager = metricsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(TaskDO taskDO) {
|
||||
try {
|
||||
|
||||
NamingService sourceNamingService =
|
||||
nacosServerHolder.get(taskDO.getSourceClusterId());
|
||||
EventListener eventListener = nacosListenerMap.remove(taskDO.getTaskId());
|
||||
PathChildrenCache pathChildrenCache = pathChildrenCacheMap.get(taskDO.getTaskId());
|
||||
sourceNamingService.unsubscribe(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
eventListener);
|
||||
CloseableUtils.closeQuietly(pathChildrenCache);
|
||||
Set<String> instanceUrlSet = instanceBackupMap.get(taskDO.getTaskId());
|
||||
CuratorFramework client = zookeeperServerHolder.get(taskDO.getDestClusterId());
|
||||
if(!instanceUrlSet.isEmpty()){
|
||||
for (String instanceUrl : instanceUrlSet) {
|
||||
client.delete().quietly().forPath(instanceUrl);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("delete task from nacos to zk was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.DELETE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(TaskDO taskDO, Integer index) {
|
||||
try {
|
||||
NamingService sourceNamingService =
|
||||
nacosServerHolder.get(taskDO.getSourceClusterId());
|
||||
CuratorFramework client = zookeeperServerHolder.get(taskDO.getDestClusterId());
|
||||
nacosListenerMap.putIfAbsent(taskDO.getTaskId(), event -> {
|
||||
if (event instanceof NamingEvent) {
|
||||
try {
|
||||
|
||||
List<Instance> sourceInstances = sourceNamingService.getAllInstances(taskDO.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), new ArrayList<>(), true);
|
||||
Set<String> newInstanceUrlSet = getWaitingToAddInstance(taskDO, client, sourceInstances);
|
||||
|
||||
// fetch the instance backup
|
||||
deleteInvalidInstances(taskDO, client, newInstanceUrlSet);
|
||||
// replace the instance backup
|
||||
instanceBackupMap.put(taskDO.getTaskId(), newInstanceUrlSet);
|
||||
// try to compensate for the removed instance
|
||||
tryToCompensate(taskDO, sourceNamingService, sourceInstances);
|
||||
} catch (Exception e) {
|
||||
log.error("event process fail, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sourceNamingService.subscribe(taskDO.getServiceName(),getGroupNameOrDefault(taskDO.getGroupName()),
|
||||
nacosListenerMap.get(taskDO.getTaskId()));
|
||||
} catch (Exception e) {
|
||||
log.error("sync task from nacos to zk was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void tryToCompensate(TaskDO taskDO, NamingService sourceNamingService, List<Instance> sourceInstances) {
|
||||
if (!CollectionUtils.isEmpty(sourceInstances)) {
|
||||
final PathChildrenCache pathCache = getPathCache(taskDO);
|
||||
// Avoiding re-registration if there is already a listener registered.
|
||||
if (pathCache.getListenable().size() == 0) {
|
||||
registerCompensationListener(pathCache, taskDO, sourceNamingService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCompensationListener(PathChildrenCache pathCache, TaskDO taskDO, NamingService sourceNamingService) {
|
||||
pathCache.getListenable().addListener((zkClient, zkEvent) -> {
|
||||
try {
|
||||
if (zkEvent.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) {
|
||||
compensateOnChildRemoval(zkClient, zkEvent, sourceNamingService, taskDO);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing ZooKeeper event: {}", zkEvent.getType(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void compensateOnChildRemoval(CuratorFramework zkClient, PathChildrenCacheEvent zkEvent, NamingService sourceNamingService, TaskDO taskDO) {
|
||||
String zkInstancePath = null;
|
||||
try {
|
||||
List<Instance> allInstances = sourceNamingService.getAllInstances(taskDO.getServiceName(), getGroupNameOrDefault(taskDO.getGroupName()));
|
||||
zkInstancePath = zkEvent.getData().getPath();
|
||||
for (Instance instance : allInstances) {
|
||||
String instanceUrl = buildSyncInstance(instance, taskDO);
|
||||
if (zkInstancePath.equals(instanceUrl)) {
|
||||
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL)
|
||||
.forPath(zkInstancePath);
|
||||
log.info("Compensated by re-creating the removed node at path: {}", zkInstancePath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to compensate for the removed node at path: {}", zkInstancePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteInvalidInstances(TaskDO taskDO, CuratorFramework client, Set<String> newInstanceUrlSet)
|
||||
throws Exception {
|
||||
Set<String> instanceBackup =
|
||||
instanceBackupMap.getOrDefault(taskDO.getTaskId(), Sets.newHashSet());
|
||||
for (String instanceUrl : instanceBackup) {
|
||||
if (newInstanceUrlSet.contains(instanceUrl)) {
|
||||
continue;
|
||||
}
|
||||
client.delete().quietly().forPath(instanceUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<String> getWaitingToAddInstance(TaskDO taskDO, CuratorFramework client,
|
||||
List<Instance> sourceInstances) throws Exception {
|
||||
HashSet<String> waitingToAddInstance = new HashSet<>();
|
||||
for (Instance instance : sourceInstances) {
|
||||
if (needSync(instance.getMetadata())) {
|
||||
String instanceUrl = buildSyncInstance(instance, taskDO);
|
||||
if (null == client.checkExists().forPath(instanceUrl)) {
|
||||
client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL)
|
||||
.forPath(instanceUrl);
|
||||
}
|
||||
waitingToAddInstance.add(instanceUrl);
|
||||
}
|
||||
}
|
||||
return waitingToAddInstance;
|
||||
}
|
||||
|
||||
protected String buildSyncInstance(Instance instance, TaskDO taskDO) throws UnsupportedEncodingException {
|
||||
Map<String, String> metaData = new HashMap<>(instance.getMetadata());
|
||||
metaData.put(SkyWalkerConstants.DEST_CLUSTER_ID_KEY, taskDO.getDestClusterId());
|
||||
metaData.put(SkyWalkerConstants.SYNC_SOURCE_KEY,
|
||||
skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId()).getCode());
|
||||
metaData.put(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY, taskDO.getSourceClusterId());
|
||||
|
||||
String servicePath = monitorPath.computeIfAbsent(taskDO.getTaskId(),
|
||||
key -> convertDubboProvidersPath(metaData.get(DubboConstants.INTERFACE_KEY)));
|
||||
|
||||
return convertDubboFullPathForZk(metaData, servicePath, instance.getIp(), instance.getPort());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* fetch zk path cache
|
||||
*
|
||||
* @param taskDO task instance
|
||||
* @return zk path cache
|
||||
*/
|
||||
private PathChildrenCache getPathCache(TaskDO taskDO) {
|
||||
return pathChildrenCacheMap.computeIfAbsent(taskDO.getTaskId(), (key) -> {
|
||||
try {
|
||||
PathChildrenCache pathChildrenCache = new PathChildrenCache(
|
||||
zookeeperServerHolder.get(taskDO.getDestClusterId()), monitorPath.get(key), false);
|
||||
pathChildrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
|
||||
return pathChildrenCache;
|
||||
} catch (Exception e) {
|
||||
log.error("zookeeper path children cache start failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
|
||||
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
|
||||
* to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
|
||||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacossync.extension.impl;
|
||||
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
||||
import com.alibaba.nacossync.cache.SkyWalkerCacheServices;
|
||||
import com.alibaba.nacossync.constant.ClusterTypeEnum;
|
||||
import com.alibaba.nacossync.constant.MetricsStatisticsType;
|
||||
import com.alibaba.nacossync.constant.SkyWalkerConstants;
|
||||
import com.alibaba.nacossync.extension.SyncService;
|
||||
import com.alibaba.nacossync.extension.annotation.NacosSyncService;
|
||||
import com.alibaba.nacossync.extension.holder.NacosServerHolder;
|
||||
import com.alibaba.nacossync.extension.holder.ZookeeperServerHolder;
|
||||
import com.alibaba.nacossync.monitor.MetricsManager;
|
||||
import com.alibaba.nacossync.pojo.model.TaskDO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.curator.framework.CuratorFramework;
|
||||
import org.apache.curator.framework.recipes.cache.TreeCache;
|
||||
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
|
||||
import org.apache.curator.utils.CloseableUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.alibaba.nacossync.util.DubboConstants.ALL_SERVICE_NAME_PATTERN;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.DUBBO_PATH_FORMAT;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.DUBBO_ROOT_PATH;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.GROUP_KEY;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.INSTANCE_IP_KEY;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.INSTANCE_PORT_KEY;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.INTERFACE_KEY;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.PROTOCOL_KEY;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.VERSION_KEY;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.WEIGHT_KEY;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.ZOOKEEPER_SEPARATOR;
|
||||
import static com.alibaba.nacossync.util.DubboConstants.createServiceName;
|
||||
import static com.alibaba.nacossync.util.NacosUtils.getGroupNameOrDefault;
|
||||
import static com.alibaba.nacossync.util.StringUtils.parseIpAndPortString;
|
||||
import static com.alibaba.nacossync.util.StringUtils.parseQueryString;
|
||||
|
||||
/**
|
||||
* @author paderlol
|
||||
* @version 1.0
|
||||
* @date: 2018-12-24 21:33
|
||||
*/
|
||||
@Slf4j
|
||||
@NacosSyncService(sourceCluster = ClusterTypeEnum.ZK, destinationCluster = ClusterTypeEnum.NACOS)
|
||||
public class ZookeeperSyncToNacosServiceImpl implements SyncService {
|
||||
|
||||
private static final String DEFAULT_WEIGHT = "1.0";
|
||||
|
||||
private final MetricsManager metricsManager;
|
||||
|
||||
/**
|
||||
* Listener cache of Zookeeper format taskId -> PathChildrenCache instance
|
||||
*/
|
||||
private final Map<String, TreeCache> treeCacheMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* service name cache
|
||||
*/
|
||||
private final Map<String, String> nacosServiceNameMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final ZookeeperServerHolder zookeeperServerHolder;
|
||||
|
||||
private final NacosServerHolder nacosServerHolder;
|
||||
|
||||
private final SkyWalkerCacheServices skyWalkerCacheServices;
|
||||
|
||||
|
||||
public ZookeeperSyncToNacosServiceImpl(ZookeeperServerHolder zookeeperServerHolder,
|
||||
NacosServerHolder nacosServerHolder, SkyWalkerCacheServices skyWalkerCacheServices,
|
||||
MetricsManager metricsManager) {
|
||||
this.zookeeperServerHolder = zookeeperServerHolder;
|
||||
this.nacosServerHolder = nacosServerHolder;
|
||||
this.skyWalkerCacheServices = skyWalkerCacheServices;
|
||||
this.metricsManager = metricsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(TaskDO taskDO, Integer index) {
|
||||
try {
|
||||
if (treeCacheMap.containsKey(taskDO.getTaskId())) {
|
||||
return true;
|
||||
}
|
||||
if (!initializeTreeCache(taskDO)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
if (destNamingService == null) {
|
||||
logAndRecordSyncError("Failed to obtain NamingService for destination clusterId: {}", taskDO.getDestClusterId(), null);
|
||||
return false;
|
||||
}
|
||||
// 初次执行任务统一注册所有实例
|
||||
registerAllInstances(taskDO, destNamingService);
|
||||
setupListener(taskDO, destNamingService);
|
||||
} catch (Exception e) {
|
||||
log.error("sync task from Zookeeper to Nacos was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private boolean initializeTreeCache(TaskDO taskDO) {
|
||||
TreeCache treeCache = getTreeCache(taskDO);
|
||||
if (treeCache == null) {
|
||||
logAndRecordSyncError("Failed to obtain TreeCache for taskId: {}", taskDO.getTaskId(), null);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private void logAndRecordSyncError(String message, String taskId, Exception e) {
|
||||
if (e != null) {
|
||||
log.error(message, taskId, e);
|
||||
} else {
|
||||
log.error(message, taskId);
|
||||
}
|
||||
metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
|
||||
}
|
||||
|
||||
private void setupListener(TaskDO taskDO, NamingService destNamingService) {
|
||||
TreeCache treeCache = Objects.requireNonNull(getTreeCache(taskDO));
|
||||
treeCache.getListenable().addListener((client, event) -> {
|
||||
try {
|
||||
// INITIALIZED is a special event that is not triggered by the Zookeeper server
|
||||
if(event.getData()==null){
|
||||
log.warn("TreeCache event data is null, taskId:{}", taskDO.getTaskId());
|
||||
return;
|
||||
}
|
||||
String path = event.getData().getPath();
|
||||
Map<String, String> queryParam = parseQueryString(path);
|
||||
if (isMatch(taskDO, queryParam) && needSync(queryParam)) {
|
||||
processEvent(taskDO, destNamingService, event, path, queryParam);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logAndRecordSyncError("Event process from Zookeeper to Nacos was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean delete(TaskDO taskDO) {
|
||||
if (taskDO.getServiceName() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CloseableUtils.closeQuietly(treeCacheMap.get(taskDO.getTaskId()));
|
||||
|
||||
try {
|
||||
NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId());
|
||||
if (destNamingService == null) {
|
||||
log.error("Failed to obtain NamingService for destination clusterId: {}", taskDO.getDestClusterId());
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> serviceNames = getServiceNamesToDelete(taskDO);
|
||||
deleteInstances(serviceNames, destNamingService, taskDO);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Delete task from Zookeeper to Nacos was failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
metricsManager.recordError(MetricsStatisticsType.DELETE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<String> getServiceNamesToDelete(TaskDO taskDO) {
|
||||
if (!ALL_SERVICE_NAME_PATTERN.equals(taskDO.getServiceName())) {
|
||||
return new HashSet<>(Collections.singleton(taskDO.getServiceName()));
|
||||
} else {
|
||||
return new HashSet<>(nacosServiceNameMap.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteInstances(Set<String> serviceNames, NamingService destNamingService, TaskDO taskDO) {
|
||||
for (String serviceName : serviceNames) {
|
||||
try {
|
||||
List<Instance> allInstances = destNamingService.getAllInstances(serviceName,
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), new ArrayList<>(), true);
|
||||
for (Instance instance : allInstances) {
|
||||
if (needDelete(instance.getMetadata(), taskDO)) {
|
||||
destNamingService.deregisterInstance(instance.getServiceName(),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), instance.getIp(), instance.getPort());
|
||||
}
|
||||
}
|
||||
nacosServiceNameMap.remove(serviceName);
|
||||
} catch (NacosException e) {
|
||||
log.error("Failed to deregister service instance for serviceName: {}", serviceName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the Path cache when the task sync
|
||||
*/
|
||||
protected TreeCache getTreeCache(TaskDO taskDO) {
|
||||
return treeCacheMap.computeIfAbsent(taskDO.getTaskId(), (key) -> {
|
||||
try {
|
||||
TreeCache treeCache = new TreeCache(zookeeperServerHolder.get(taskDO.getSourceClusterId()),
|
||||
DUBBO_ROOT_PATH);
|
||||
treeCache.start();
|
||||
return treeCache;
|
||||
} catch (Exception e) {
|
||||
log.error("zookeeper path children cache start failed, taskId:{}", taskDO.getTaskId(), e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the instance information needs to be synchronized based on the dubbo version, grouping name,
|
||||
* and service name.
|
||||
*/
|
||||
protected boolean isMatch(TaskDO taskDO, Map<String, String> queryParam) {
|
||||
return isVersionMatch(taskDO, queryParam) &&
|
||||
isGroupMatch(taskDO, queryParam) &&
|
||||
isServiceMatch(taskDO, queryParam) ||
|
||||
isMatchAllServices(taskDO);
|
||||
}
|
||||
|
||||
private boolean isVersionMatch(TaskDO task, Map<String, String> queryParam) {
|
||||
return StringUtils.isBlank(task.getVersion()) || StringUtils.equals(task.getVersion(), queryParam.get(VERSION_KEY));
|
||||
}
|
||||
|
||||
private boolean isGroupMatch(TaskDO task, Map<String, String> queryParam) {
|
||||
return StringUtils.isBlank(task.getGroupName()) || StringUtils.equals(task.getGroupName(), queryParam.get(GROUP_KEY));
|
||||
}
|
||||
|
||||
private boolean isServiceMatch(TaskDO task, Map<String, String> queryParam) {
|
||||
return StringUtils.isNotBlank(task.getServiceName()) && StringUtils.equals(task.getServiceName(), queryParam.get(INTERFACE_KEY));
|
||||
}
|
||||
|
||||
private boolean isMatchAllServices(TaskDO task) {
|
||||
return StringUtils.isNotBlank(task.getServiceName()) && StringUtils.equals(task.getServiceName(), ALL_SERVICE_NAME_PATTERN);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds a synchronized Nacos instance from Zookeeper data.
|
||||
*
|
||||
* @param queryParam Parameters obtained from the query string.
|
||||
* @param ipAndPortMap IP and port information.
|
||||
* @param taskDO Task details.
|
||||
* @return A fully configured Nacos instance.
|
||||
*/
|
||||
protected Instance buildSyncInstance(Map<String, String> queryParam, Map<String, String> ipAndPortMap, TaskDO taskDO) {
|
||||
Instance instance = new Instance();
|
||||
instance.setIp(ipAndPortMap.get(INSTANCE_IP_KEY));
|
||||
instance.setPort(Integer.parseInt(ipAndPortMap.get(INSTANCE_PORT_KEY)));
|
||||
instance.setServiceName(getServiceNameFromCache(taskDO.getTaskId(), queryParam));
|
||||
instance.setWeight(parseWeight(queryParam));
|
||||
instance.setHealthy(true);
|
||||
instance.setMetadata(buildMetadata(queryParam, ipAndPortMap, taskDO));
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
private double parseWeight(Map<String, String> queryParam) {
|
||||
try {
|
||||
return Double.parseDouble(queryParam.getOrDefault(WEIGHT_KEY, DEFAULT_WEIGHT));
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Error parsing weight: {}", queryParam.get(WEIGHT_KEY), e);
|
||||
return Double.parseDouble(DEFAULT_WEIGHT); // Default weight in case of error
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> buildMetadata(Map<String, String> queryParam, Map<String, String> ipAndPortMap, TaskDO taskDO) {
|
||||
Map<String, String> metaData = new HashMap<>(queryParam);
|
||||
metaData.put(PROTOCOL_KEY, ipAndPortMap.get(PROTOCOL_KEY));
|
||||
metaData.put(SkyWalkerConstants.DEST_CLUSTER_ID_KEY, taskDO.getDestClusterId());
|
||||
metaData.put(SkyWalkerConstants.SYNC_SOURCE_KEY, skyWalkerCacheServices.getClusterType(taskDO.getSourceClusterId()).getCode());
|
||||
metaData.put(SkyWalkerConstants.SOURCE_CLUSTER_ID_KEY, taskDO.getSourceClusterId());
|
||||
return metaData;
|
||||
}
|
||||
|
||||
private void processEvent(TaskDO taskDO, NamingService destNamingService, TreeCacheEvent event, String path,
|
||||
Map<String, String> queryParam) throws NacosException {
|
||||
if (!com.alibaba.nacossync.util.StringUtils.isDubboProviderPath(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, String> ipAndPortParam = parseIpAndPortString(path);
|
||||
if (ipAndPortParam.isEmpty()) {
|
||||
log.error("Invalid IP and Port data extracted from path: {}", path);
|
||||
return;
|
||||
}
|
||||
Instance instance = buildSyncInstance(queryParam, ipAndPortParam, taskDO);
|
||||
String serviceName = queryParam.get(INTERFACE_KEY);
|
||||
if (serviceName == null || serviceName.isEmpty()) {
|
||||
log.error("Service name is missing in the query parameters.");
|
||||
return;
|
||||
}
|
||||
switch (event.getType()) {
|
||||
case NODE_ADDED:
|
||||
case NODE_UPDATED:
|
||||
|
||||
destNamingService.registerInstance(getServiceNameFromCache(serviceName, queryParam),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), instance);
|
||||
break;
|
||||
case NODE_REMOVED:
|
||||
|
||||
destNamingService.deregisterInstance(getServiceNameFromCache(serviceName, queryParam),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), ipAndPortParam.get(INSTANCE_IP_KEY),
|
||||
Integer.parseInt(ipAndPortParam.get(INSTANCE_PORT_KEY)));
|
||||
nacosServiceNameMap.remove(serviceName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void registerAllInstances(TaskDO taskDO, NamingService destNamingService) throws Exception {
|
||||
CuratorFramework zk = zookeeperServerHolder.get(taskDO.getSourceClusterId());
|
||||
if (!ALL_SERVICE_NAME_PATTERN.equals(taskDO.getServiceName())) {
|
||||
registerALLInstances0(taskDO, destNamingService, zk, taskDO.getServiceName());
|
||||
} else {
|
||||
// 同步全部
|
||||
List<String> serviceList = zk.getChildren().forPath(DUBBO_ROOT_PATH);
|
||||
for (String serviceName : serviceList) {
|
||||
registerALLInstances0(taskDO, destNamingService, zk, serviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerALLInstances0(TaskDO taskDO, NamingService destNamingService, CuratorFramework zk,
|
||||
String serviceName) throws Exception {
|
||||
String path = String.format(DUBBO_PATH_FORMAT, serviceName);
|
||||
if (zk.getChildren() == null) {
|
||||
return;
|
||||
}
|
||||
List<String> providers = zk.getChildren().forPath(path);
|
||||
for (String provider : providers) {
|
||||
Map<String, String> queryParam = parseQueryString(provider);
|
||||
if (isMatch(taskDO, queryParam) && needSync(queryParam)) {
|
||||
Map<String, String> ipAndPortParam = parseIpAndPortString(path + ZOOKEEPER_SEPARATOR + provider);
|
||||
Instance instance = buildSyncInstance(queryParam, ipAndPortParam, taskDO);
|
||||
destNamingService.registerInstance(getServiceNameFromCache(serviceName, queryParam),
|
||||
getGroupNameOrDefault(taskDO.getGroupName()), instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* cteate Dubbo service name
|
||||
*
|
||||
* @param serviceName dubbo service name
|
||||
* @param queryParam dubbo metadata
|
||||
*/
|
||||
protected String getServiceNameFromCache(String serviceName, Map<String, String> queryParam) {
|
||||
return nacosServiceNameMap.computeIfAbsent(serviceName, (key) -> createServiceName(queryParam));
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue