Compare commits

...

409 Commits

Author SHA1 Message Date
Sunrisea e868a001ec
feat: add default log rolling config , update version of nacos go client (#841)
* add default log rolling config , update version of nacos go client

* add v2.2.9 deprecated information
2025-08-18 15:09:40 +08:00
Sunrisea 8bad3fed0f
Update grpc version and go version (#839)
* Update Grpc and protobuf version

* fix version

* fix workflow

* fix codestyle

* fix go mod
2025-08-14 11:29:27 +08:00
Sunrisea 6d3e89ff89
fix: Supports multiple subscriptions to services across different clusters. (#838)
* Supports multiple subscriptions to services across different clusters.

* fix bug
2025-08-13 19:08:52 +08:00
shalk(xiao kun) ead2368c2f
fix: nacos client unsubscribe (#836) 2025-08-13 11:01:30 +08:00
Sunrisea eb3adf0f0a
Fix search config bug (#828) 2025-05-28 15:48:02 +08:00
Sunrisea 2bc352f538
Fix Nacos 3.0 Search Config bug (#825) 2025-05-18 16:03:35 +08:00
Sunrisea a0fc325190
Support Nacos 3.0 Search Config (#824) 2025-05-15 14:29:52 +08:00
Xin Luo 5758f57b6b
Fix: fix deadlock when close client multi times (#817)
* Fix: fix deadlock when close client multi times

* Fix: Rename isClose to isClosed
2025-05-15 14:26:31 +08:00
blake.qiu 310a82873f
fix: resubscribe to previously subscribed configurations after rpcClient reconnects to the server (#802)
fix: resubscribe to previously subscribed configurations after rpcClient reconnects to the server
2025-05-15 14:24:49 +08:00
blake.qiu f1545e0403 fix: since the program will only log in once, there is no way to refresh the token. 2025-05-15 14:23:20 +08:00
Sunrisea 741b6adca7
fix(#811): Remove error log when matched ramCredentialProvider not found 2025-05-13 20:40:33 +08:00
Sunrisea 98686b0b0a fix no matched provider log 2025-05-07 10:55:22 +08:00
Xin Luo 9df154169b
fix error request type name (#810) 2025-04-07 17:15:08 +08:00
zeyu-zhang 476a2c4fa0
Add support for customized credentials provider (#800)
Signed-off-by: Zeyu Zhang <zhangzeyu.zzy@alibaba-inc.com>
2025-03-18 16:40:43 +08:00
Sunrisea 3bbe1db11e
Refactor the client auth module and support more aliyun ram auth mode (#797)
* refactor client auth module, support more mode
2025-02-25 20:07:39 +08:00
Sunrisea c925bcf60d
support config_tags (#799) 2025-02-25 20:06:26 +08:00
Sunrisea 6f5bdc4cb6
Fix the bug of callback not triggered on client when config deleted (#796)
* fix get empty config bug

* fix test bug

* fix test bug
2025-02-12 17:19:03 +08:00
dependabot[bot] 2f0372c760
Bump golang.org/x/net from 0.23.0 to 0.33.0 (#793)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-23 17:02:09 +08:00
yanxyc b098c04ffb
Update nacos_grpc_service.proto (#783) 2025-01-21 17:07:40 +08:00
Sunrisea 7c3b1dca16
配置加密功能支持阿里云KMS RAM 访问方式 (#788)
* support aliyun kms ram mode
2025-01-21 17:07:26 +08:00
CZJCC 25436c0fcf
新增http请求参数url encode,防止特殊字符后端处理报错 (#786) 2024-11-26 16:22:16 +08:00
cinience 707bd574c7
Fixed bool format (#782) 2024-11-18 19:32:05 +08:00
binbin.zhang c3c6011815
update getFailOverConfig log level (#768) 2024-07-14 16:48:22 +08:00
杨春 a0c848a550
fix #736, [掉线重连可能导致服务丢失](https://github.com/nacos-group/nacos-sdk-go/issues/736) (#737)
Co-authored-by: yangchun2 <yangchun2@shopline.com>
2024-06-13 09:44:30 +08:00
dingyang666 b2c297e409
Fix InitLogger just no work (#759)
* Fix InitLogger just no work

* Delete once
2024-06-13 09:43:56 +08:00
binbin.zhang 61ecaa8163
refine redo subscribe (#758) 2024-06-02 15:52:04 +08:00
binbin.zhang 083c02f5a6
grpc add log (#757) 2024-06-02 15:44:07 +08:00
huangjikun 006f55d6e1
fix: auth login failed in address mode. (#728) (#730) 2024-06-02 15:15:05 +08:00
dependabot[bot] 2e0eb3c25e
Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 (#732)
Bumps google.golang.org/protobuf from 1.30.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-02 15:14:24 +08:00
nov.lzf 7d85b858b4
support app connection labels (#754)
* 支持应用标签
2024-05-27 15:24:26 +08:00
wangjian 163ee14e36
fix: log unit test failed (#734) 2024-05-06 09:57:37 +08:00
程露 6763a4e271
fix panic when server push request has request headers. (#739) 2024-04-25 17:53:26 +08:00
nil 6f9348ae4c
🐞 fix: (#740)
Adjust lock order to address concurrency issue  Moved defer ed.mux.Unlock() after ed.mux.Lock() to ensure immediate unlocking after acquiring the lock, resolving potential concurrency issues.
2024-04-25 17:53:02 +08:00
dependabot[bot] 169486a47a
Bump golang.org/x/net from 0.17.0 to 0.23.0 (#748)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-25 17:50:08 +08:00
nov.lzf 690bd7beb7
nacos-go-sdk support grpc tls (#746)
* support grpc tls
2024-04-25 17:48:48 +08:00
Kurisu_Amatist 6dd8997f7a
dev (#722) 2024-03-05 17:14:05 +08:00
程露 389f6b6dbd
fix: the monitor in naming_grpc_proxy. (#717) 2024-01-26 14:56:03 +08:00
brother-戎 89c5ab6a74
[reconstruction] change configFilterChain from public sigleton to private owned by ConfigClient (#700)
* change configFilterChain from public sigleton to private owned by ConfigClient

* fix up encryptedDataKey cache

* tiny fix

* tiny fix

* change mse endpoint

* add using localDiskCache test case

* add using localDiskCache test case

* add using localDiskCache test case
2023-12-22 13:37:16 +08:00
brother-戎 7178ed341c
[version compatibility] fix up when response.ResultCode is 200 but response.Success is false (#699)
* fix up when response.ResultCode is 200 but is not success

* fix up when response.ResultCode is 200 but is not success

* fix up when response.ResultCode is 200 but is not success

* fix up when response.ResultCode is 200 but is not success

* tiny fix
2023-12-22 13:36:56 +08:00
dependabot[bot] 81bf7d5a0f
Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#701)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 10:36:47 +08:00
brother-戎 8677a56468
[bug fix] fixup config listen cache empty (#697)
* [bug fix] fixup config listen cache empty
2023-12-14 15:04:34 +08:00
Lxtend 65e2c9d5e8
fix: repair for updateCacheWhenEmpty not working (#696) 2023-12-12 16:58:44 +08:00
brother-戎 5fedf574d9
fix up encryption logger: instead if logging when init() to createConfigClient (#690) 2023-12-05 18:50:52 +08:00
Albumen Kevin 355c2fd062
Add Request-Module header when requesting endpoint (#685)
* Add Request-Module header when requesting endpoint

* Sync headers
2023-11-30 19:25:37 +08:00
brother-戎 d934e30359
[bug fix] fix up encryption logger level to debug mod (#688)
* fix up encryption logger level to debug mod
2023-11-30 19:12:56 +08:00
杨翊 SionYang 28188c7967
Update go client version to 2.2.4 (#682)
Update go client version to 2.2.4
2023-11-10 15:39:29 +08:00
brother-戎 03026b7e01
[fix]: add some log for naming selectInstances (#681)
* add log for naming selectInstances

* add some error logs for config

* fix up
2023-11-10 15:31:28 +08:00
brother-戎 79bf755989
ca is not necessary (#680) 2023-11-09 14:56:21 +08:00
dependabot[bot] 4ac57745c5
Bump google.golang.org/grpc from 1.53.0 to 1.56.3 (#672)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.53.0 to 1.56.3.
2023-11-08 20:19:17 +08:00
brother-戎 9b9ff77177
[feature] support aes-128 & aes-256 encryption (#668)
* support aes-128 & aes-256 encryption
2023-11-08 20:18:39 +08:00
Lxtend 6930f82f09
feat: export ServerHealthy in INamingClient (#677) 2023-11-08 19:47:05 +08:00
nov.lzf 4eb055946d
Develop endpoint (#673)
* support endpoint params

* support endpoint context path
2023-11-01 18:52:46 +08:00
weijie f657a269e7
refactor: move from ioutil to io packages (#661)
* refactor: move from ioutil to io packages

---------

Co-authored-by: akatsuke <8422126+akatsukee@user.noreply.gitee.com>
2023-10-20 18:51:18 +08:00
dependabot[bot] e4312deb60
Bump golang.org/x/net from 0.7.0 to 0.17.0 (#666)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 19:56:45 +08:00
sodaRyCN 9653ddea22
log it when response is fail and no error (#652)
* log when response is fail and no error
2023-09-01 16:46:46 +08:00
binbin.zhang b51d87d10b
config client cache data race (#645)
* refine
2023-08-07 14:29:42 +08:00
binbin.zhang d4949e187b
fix server change event (#643) 2023-08-07 10:05:43 +08:00
binbin.zhang bb9e2ce276
refine config_client listen config (#644) 2023-08-07 10:05:18 +08:00
binbin.zhang b8f432f437
fix cacheData race (#642) 2023-08-02 18:48:55 +08:00
binbin.zhang 6e50fb9d8d
fix cacheData race (#641) 2023-08-01 10:15:10 +08:00
binbin.zhang 626117a7ec
del init print log path 2023-07-24 19:51:10 +08:00
dependabot[bot] 0d34e0a237
Bump golang.org/x/net from 0.5.0 to 0.7.0 (#636)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.7.0.
- [Commits](https://github.com/golang/net/compare/v0.5.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 09:56:11 +08:00
binbin.zhang e4f8b4e8a1
upgrade grpc version v1.53.0 (#635)
* upgrade grpc version v1.53.0

* upgrade go version

* update go mod
2023-07-19 20:38:52 +08:00
杨春 90174c2023
ConfigParam add SrcUser
Co-authored-by: yangchun2 <yangchun2@joyy.com>
2023-06-24 17:42:49 +08:00
杨翊 SionYang 16975cb816
Master sync java enhancement (#617)
* set success as True when ack push success.

* Same as java client, default close async update service by query.
2023-05-27 20:34:02 +08:00
binbin.zhang a23dd577d8
v2.2.2 (#615) 2023-05-17 20:39:59 +08:00
realJackSun f113a815be
Fix auth problem (#614)
* rewrite the Id in ConfigItem

* Change cr.GetGroup to cr.GetTenant.

* Revert "rewrite the Id in ConfigItem"

This reverts commit 6d4d84eb
2023-05-17 14:28:04 +08:00
binbin.zhang dfbe390d78
fix read config cache (#600) 2023-04-01 13:05:36 +08:00
binbin.zhang c11cbcc5ca
grpc default port init (#598)
* grpc default port init

* update version
2023-03-23 19:11:31 +08:00
lin e25bc91bb0
修复初始化并发读写的情况 (#591)
* 修复初始化并发写的情况
2023-03-23 17:21:31 +08:00
Cotch 43cf5aeb30
调整 [INFO] logDir cacheDir 日志输出 (#590)
* 使用 logger 替换 log 在配置时的输出
2023-03-23 09:54:22 +08:00
binbin.zhang 859cdce5a7
fixed memory usage problem caused by maxInt chan (#596) 2023-03-22 21:05:21 +08:00
binbin.zhang 03bd50fb6e
update get cache config log print (#594) 2023-03-21 16:27:25 +08:00
binbin.zhang 73c4f3632d
update version (#589) 2023-03-15 19:09:33 +08:00
binbin.zhang 802920dd32
update ConnectionEvent capacity (#588) 2023-03-15 19:03:53 +08:00
binbin.zhang 82a862a9a4
fix QueryInstancesOfService clusters (#578) 2023-02-22 10:15:22 +08:00
binbin.zhang e190e290f5
fix panic for rpc errorResponse (#577) 2023-02-21 19:28:41 +08:00
Bave Lee 8c02e5d388
Fix MaxInt64 overflows on ARM 32-bits (#575)
* Fix MaxInt64 overflows on ARM 32-bits
2023-02-21 19:05:38 +08:00
binbin.zhang 2f8d81c152
refine grpc_client (#574) 2023-02-15 20:44:16 +08:00
binbin.zhang 4cddffb74f
support batch register (#573)
* support batch register

* batch register ut
2023-02-15 20:15:43 +08:00
wang.dongyun 63f4c7b8aa
[conf] 配置中心关于本地文件缓存问题 (#565)
* 远程nacos故障,降级本地

Co-authored-by: jeff.wang <jeff.wang@lunarax.io>
2023-02-03 19:01:33 +08:00
binbin.zhang 3b038b4122
Master namespace id dafult (#558)
* default namespaceId

* update version
2023-01-16 19:56:46 +08:00
haifeiWu cb837151ff
update README go format (#553)
update README go format
2023-01-12 19:46:55 +08:00
a3d21 8fb78d150a
Fix listener receiving encrypted content when kms is open. (#549)
* Fix listener receiving encrypted content when kms is open.
2022-12-26 11:24:28 +08:00
binbin.zhang c819fd3bce
refine (#552)
fixes: 545
2022-12-26 10:53:39 +08:00
binbin.zhang df401c22f4
fix config listener cpu usage (#551) 2022-12-12 19:56:20 +08:00
haifeiWu c4a995cccd
fix error in example code (#546)
fix error in example code
2022-12-10 21:34:28 +08:00
DangoFish 10614a56d6
Update README_CN.md (#541) 2022-11-28 21:28:36 +08:00
Pixy Yuan 61c06ae6f7
[ISSUE #537] Fix naming http client authentication. (#538) 2022-11-21 21:11:34 +08:00
binbin.zhang a13f6f2403
update SearchConfigParam (#535) 2022-10-29 15:55:48 +08:00
binbin.zhang cad5a5ec2b
update go.yml (#534) 2022-10-27 20:50:48 +08:00
binbin.zhang d7d2210404
Update go.yml
update go.yml 1.17
2022-10-27 20:41:52 +08:00
binbin.zhang 7f723c12da
update version 2.1.1 (#533) 2022-10-27 20:21:54 +08:00
summer-endless 8be361d514
fix issue #519 (#520)
* fix issue #519,当NotLoadCacheAtStart设置为false,第二次启动,服务订阅Subscribe不生效问题
2022-10-13 11:22:58 +08:00
LanLanceYuan 3996df22e0
chore: fix typo (#525) 2022-10-13 11:21:10 +08:00
binbin.zhang c085cfbc3a
use context cancel leak goroutine (#523) 2022-10-01 21:38:31 +08:00
binbin.zhang c65ee73bd7
update configitem.id type (#512) 2022-09-01 10:33:21 +08:00
lin bdae4568ee
enhance grpc time out (#511) 2022-09-01 10:30:18 +08:00
binbin.zhang 3dffcd5f57
replace service_info_holder concurrent map with sync.map (#501) 2022-08-23 21:13:26 +08:00
binbin.zhang b5986f7424
fix getServieList use namespaceId (#500)
* fix getServieList use namespaceId
2022-08-14 13:30:28 +08:00
binbin.zhang 373906a4be
2.0.x (#496) 2022-07-30 17:21:35 +08:00
wangcong dec3976954
fix: fix ConfigClient data race (#483) 2022-07-21 22:17:46 +08:00
孙鹏 5cb07119d4
fixes syntax error for Readme doc (#492) 2022-07-21 22:13:36 +08:00
志飞 c7dfb75b3c
bugfix dubbo-go issue: nacos-go-sdk stdout redirect #1960 (#489)
fix:490
Co-authored-by: chenzf72 <chenzf72@chinaunicom.cn>
2022-07-17 15:51:18 +08:00
Zhiqiang Li 24e67a3e0a
optimize code (#471) (#484) 2022-07-05 19:49:54 +08:00
binbin.zhang bcceb1967c
fix test race (#445) 2022-04-11 09:37:16 +08:00
binbin.zhang 54008acea2
ci add race check (#444) 2022-04-10 15:39:48 +08:00
kinggo bff55a2174
fix: InitLogger data-race (#438) 2022-04-07 22:33:35 +08:00
binbin.zhang c23c0c2d0a
refine custom logger (#436) 2022-04-06 18:57:27 +08:00
binbin.zhang c67e862fc9
add issue template (#433) 2022-04-02 17:53:26 +08:00
realJackSun 9839234948
Merge pull request #405 from HabbyChen/master
注入对日志接口的实现
2022-03-31 20:36:43 +08:00
realJackSun 3e3cfdd9bc Remove redundant code 2022-03-31 14:25:35 +08:00
realJackSun 388150aa3b Change the LOG_FILE_NAME from ./nacos-sdk.log to nacos-sdk.log 2022-03-31 14:25:17 +08:00
realJackSun 99ec14799e Change the LogDir 2022-03-31 14:24:47 +08:00
realJackSun c27b60e2bb Remove redundant code 2022-03-31 14:24:08 +08:00
realJackSun 761002fa1c Remove redundant 2022-03-31 14:23:08 +08:00
realJackSun 3698825f50 Reorder the imports 2022-03-31 14:22:33 +08:00
habby_chen f9ad5fa5c6
Merge branch 'master' into master 2022-03-21 19:17:56 +08:00
habby fe60728e0b 删除无用的缓存 2022-03-21 19:15:14 +08:00
habby 7d29499c7a 参数遗漏 2022-03-21 19:12:51 +08:00
habby 8e2a3aea7f 处理LogRollingConfig的配置引入问题 2022-03-21 19:10:11 +08:00
habby bc7faf51f4 constant.LOG_FILE_NAME导致的循环引入的问题 2022-03-21 19:07:07 +08:00
Yang d73b05025e
Merge pull request #419 from Sivyer9303/bugfix_#285
Change the way of compare service hosts.
2022-03-20 19:33:24 +08:00
sivyer9303 fe3bf00282 add more unit test 2022-03-20 14:17:17 +08:00
sivyer9303 246004a98f copy new instances when comparing old instances with new instances. 2022-03-18 10:59:50 +08:00
sivyer9303 d087325ddb remove sortInstance to host_reactor 2022-03-10 19:41:25 +08:00
sivyer9303 8866dd0927 fix review. 2022-03-09 11:12:29 +08:00
sivyer9303 9a5c926cfb bugfix.
change the way of compare instance.
2022-03-08 20:50:58 +08:00
binbin.zhang 6ab22f1674
log support stdout (#415) 2022-03-02 11:00:30 +08:00
binbin.zhang 3cad874150
refine push receiver start udp (#414) 2022-02-25 18:24:06 +08:00
habby dcc2380087 完成了全部的日志的注入 2022-02-25 16:00:22 +08:00
habby 169f8fe499 完成了全部的日志的注入 2022-02-25 15:57:45 +08:00
habby f78d9a6bbc 删掉不要的日志 2022-02-21 11:33:45 +08:00
habby 9b8e9e991b 调整了配置,日志配置在日志里面 2022-02-21 11:32:21 +08:00
habby bcc167f450 注入对日志接口的实现 2022-02-14 11:03:26 +08:00
habby b4448a75a0 注入对日志接口的实现 2022-02-14 10:43:15 +08:00
binbin.zhang 4bbba409c2
sync semaphore (#396) 2022-02-08 20:21:54 +08:00
binbin.zhang 4461728c65
ignore Listen config cache file err (#393) 2022-01-27 14:49:30 +08:00
binbin.zhang 34098b2580
1.x update readme (#383) 2022-01-25 09:42:35 +08:00
binbin.zhang e0a4cfa252
update readme (#382) 2022-01-25 09:34:10 +08:00
binbin.zhang 5cab3c4a8c
add lumberjack logs can be divided by size and time (#381) 2022-01-25 09:15:32 +08:00
binbin.zhang 05958d8160
add healthy for subscribe struct (#380) 2022-01-24 09:43:47 +08:00
Chengshihao1018 77813239f1
fix race in naming_client (#371)
Co-authored-by: chengshihao <chengshihao@xiaomi.com>
2022-01-19 09:36:56 +08:00
binbin.zhang 6d3930d845
deepcopy matedata map 2021-12-03 09:15:49 +08:00
赵延 e3f29806b1
use or to check refresh server list or not. (#334)
Co-authored-by: horizonzy <horizon@apache.org>
2021-12-02 09:47:31 +08:00
binbin.zhang 0ab7580ef8
add pageNo&pageSize default value (#332) 2021-11-25 09:39:55 +08:00
binbin.zhang 76d51a48d8
refine code (#333) 2021-11-24 13:43:43 +08:00
包子 caf97766c0
deps: upgrade jsonparser (#329) 2021-11-19 15:34:10 +08:00
binbin.zhang 3b96bb2902
refine req server error log (#323) 2021-11-12 21:13:17 +08:00
binbin.zhang dd643a38fd
update get config log level (#321) 2021-11-09 22:42:46 +08:00
Gerrard-YNWA 554da083d4
change: rename to host_reactor.go (#315) 2021-10-25 09:50:44 +08:00
binbin.zhang 15bb5d2905
scheduler delay is 500 ms (#312) 2021-10-14 09:22:48 +08:00
binbin.zhang 9043c66b4d
Optimizing folder creation (#305) 2021-09-11 11:10:04 +08:00
binbin.zhang 7863252f67
fcix AddBeatInfo func goroutine leak (#297) 2021-09-09 09:53:59 +08:00
Gagharv a7badf0ece
support config type (#296)
* support config type
2021-08-22 16:46:53 +08:00
Nick Gu 868f01f0ca
fix: init NamingClient.INacosClient (#295) 2021-08-21 09:46:58 +08:00
Frank Yang 806558d9bf
Add function UpdateInstance() #286 (#287)
* add function UpdateInstance()
2021-08-21 09:45:17 +08:00
Zhiqiang Li 8e5874fb9f
Add log sampling support (#278)
* Add log sampling support
2021-08-12 19:18:20 +08:00
ON10N 1fd4972c8b
Fixes #222, for the char is not urldecode (#224)
* Fixes #222, for the char is not urldecode
2021-06-21 23:17:12 +08:00
ilib0x00000000 aab17cfbe0
use logger print log (#260) (#261)
* use logger print log (#260)
2021-06-11 11:17:02 +08:00
binbin.zhang 56cb33f77a
remove init to start the scheduling task (#251) 2021-05-27 10:43:18 +08:00
tux f36639820a
Repair the cache pollution caused by v.tenat != clientConfig.NamespaceId (#221) 2021-05-23 22:03:56 +08:00
binbin.zhang 3e368e5143
fixed duplicate random numbers (#246) 2021-05-23 22:02:37 +08:00
Himanshu 84fad6d923
handle panic push_receiver (#243)
* handle panic push_receiver
Co-authored-by: Himanshu Ranjan <himanshuranjan@appointy.com>
2021-05-08 22:39:58 +08:00
Shuhui Xu 0a9999ce9c
fix: SearchConfigParm typo (#238) 2021-05-01 11:38:47 +08:00
adair peng fe00149897
fix:因获取local ip错误导致无法接收到sever的udp包 (#208) (#214) 2021-04-30 09:20:12 +08:00
Shuhui Xu 5516ed97d4
fix: error WithPort func (#228)
Co-authored-by: 徐曙辉 <xushuhui@himango.cn>
2021-04-29 17:24:09 +08:00
Liu Guo Yang 86b5705484
当认证不通过时,创建client返 回成功(主要兼容nacos挂掉的情况) (#211)
* 当认证不通过时, 打日志,创建client返 回成功

* update default refresh token 5 second

Co-authored-by: liuguoyang1 <liuguoyang1@xdf.cn>
2021-04-19 23:13:17 +08:00
Scout Wang 793608795d
When connection has not been established, close it directly would cause nil pointer exception. (#213) 2021-04-18 20:31:57 +08:00
brothelul abd512c238
remove the return error when not find the config (#212) 2021-04-16 08:59:20 +08:00
赵云兴 c9952e6dde
fix:修复windows文件名称问题 (#205)
* fix:修复windows文件名称问题
Co-authored-by: sanxun0325 <bbz17640380550@163.com>
2021-04-08 09:49:43 +08:00
binbin.zhang 54f875c20a
refine SetClientConfig (#199) 2021-04-06 09:11:45 +08:00
binbin.zhang d95b980be7
Add namespace value description (#200) 2021-04-01 09:26:36 +08:00
包子 b82babf435
fix: 当服务名称为空时导致 nacos 服务异常的问题 (#202)
RegisterInstance func adds serviceName blank character validation
2021-03-30 21:23:51 +08:00
Liu Guo Yang a63411bd5a
delete std log when http_agent put err (#198)
Co-authored-by: liuguoyang1 <liuguoyang1@xdf.cn>
2021-03-25 20:50:36 +08:00
lzp0412 d75caca21a
Merge pull request #196 from lzp0412/master
Trigger callback when subscribe service
2021-03-25 19:11:44 +08:00
lzp0412 b1baa287fa fix review comment 2021-03-25 18:05:12 +08:00
ICannerxy fd641a0d88
fix: fix client app name and client ip (#195)
*app parameters are supported by the ClientConfig

Co-authored-by: xuyang <wb-xy657181@antgroup.com>
2021-03-25 16:36:33 +08:00
lzp0412 3ac7deafeb trigger callback when subscribe service 2021-03-24 17:30:50 +08:00
lzp0412 9edc707e75
Merge pull request #193 from SpecialYang/master
Fix default timeoutms config
2021-03-12 10:37:37 +08:00
SpecialYang 6b25657ac4 Fix default timeoutms config 2021-03-11 21:07:27 +08:00
lzp0412 14f269ee45
Merge pull request #181 from undom/fix-deregister-data-race
Fix deregister data race(#180)
2021-02-01 21:26:43 +08:00
mengxiaodong e8f23a1552 fix Test_SearchConfig 2021-02-01 12:30:02 +08:00
sanxun0325 7e41051661 fix deregister data race: use atomic oprate 2021-02-01 11:54:38 +08:00
mengxiaodong 91c7002e15 fix deregister data race: use atomic oprate 2021-02-01 11:54:25 +08:00
mengxiaodong c0c8155424 Formatting and typos fixes 2021-02-01 11:54:25 +08:00
binbin.zhang 4d41ccdcd3
refine Test_SearchConfig 2021-02-01 09:19:43 +08:00
lzp0412 00a9636f88
Merge pull request #186 from zensh/master
remove base64 encode for KMS
2021-01-31 10:59:14 +08:00
zensh 6554df298a remove base64 encode for KMS 2021-01-29 11:06:57 +08:00
lzp0412 2a794b27ca
Merge pull request #185 from lzp0412/master
Support pulish agg config
2021-01-20 21:37:18 +08:00
lzp0412 83ebd29c1e fix ut of Test_SearchConfig 2021-01-20 21:11:59 +08:00
lzp0412 036941be9c Merge remote-tracking branch 'remotes/nacos/master' 2021-01-20 20:48:01 +08:00
lzp0412 325c97ca0c add remove agg interface 2021-01-20 14:16:04 +08:00
lzp0412 b15ed8c5a6
Merge pull request #184 from zensh/master
fix KMS feature
2021-01-19 19:30:24 +08:00
zensh d006805d58 fix KMS feature 2021-01-15 15:52:48 +08:00
binbin.zhang c28737bcd2
fix ut (#182) 2021-01-14 22:07:33 +08:00
Liu Guo Yang fbe2919bf5
fix logger data race
fix logger data race
2021-01-14 13:28:53 +08:00
Liu Guo Yang 7e6b2e376e
154 client factory params change to options (#171)
create client with param struct
2021-01-14 13:28:02 +08:00
lzp0412 0d67c7f747 fix return error bug 2021-01-13 19:31:27 +08:00
lzp0412 870d5825ee add context path 2021-01-13 10:51:37 +08:00
lzp0412 f9ae565faf fix publish agg bug 2021-01-13 10:32:31 +08:00
lzp0412 306e337500 add publish agg 2021-01-13 10:22:06 +08:00
lzp0412 e7a2432c3f Merge remote-tracking branch 'remotes/nacos/master' 2021-01-12 21:56:37 +08:00
Liu Guo Yang b1d375a297
fix conflicts, when multiple versions are running at the same time
Co-authored-by: liuguoyang1 <liuguoyang1@xdf.cn>
2021-01-03 21:47:47 +08:00
mark4z 064792389a update doc and example for options 2020-12-16 09:55:59 +08:00
mark4z 7f5007a37c client_config_options.go 2020-12-16 09:55:59 +08:00
mark4z a09fda23fc client_config_options.go 2020-12-16 09:55:59 +08:00
mark4z b3d3bde253 client_config_options.go 2020-12-16 09:55:59 +08:00
mark4z d02f2a1ba0 config init 2020-12-16 09:55:59 +08:00
mark4z 96759bed22 config init 2020-12-16 09:55:59 +08:00
Ray Wong 3fd2a729f1
Fixed error log printing in ListenConfig()
Fixed error log printing in ListenConfig()
2020-12-11 17:32:47 +08:00
yap12138 c245d6d802
fix server config scheme lose (#163) 2020-12-09 10:17:21 +08:00
binbin.zhang 35225d6b8f
Count the number of listeners using Concurrentmap.count (#160) 2020-12-09 10:16:24 +08:00
lzp0412 fab32c8acb
Merge pull request #162 from nacos-group/debug-prom
fix: get  empty server list from endpoint
2020-12-08 13:51:51 +08:00
lzp0412 5a038ddcaa fix unit test fail bug 2020-12-08 12:03:16 +08:00
lzp0412 e3b0931826 delete debug log 2020-12-08 10:07:05 +08:00
lzp0412 60774f8a40 fix serverlist empty bug 2020-12-07 16:30:35 +08:00
lzp0412 661e1b47c2 debug pro 2020-12-07 15:41:47 +08:00
binbin.zhang 762827f8ca
Count the number of listeners using Concurrentmap.count (#159) 2020-12-04 15:33:01 +08:00
adair peng 58a3b62b9b
filter loopback ip (#153) 2020-11-30 21:49:33 +08:00
lzp0412 2255bad7c4
Merge pull request #151 from mark4z/master
fix ServerConfig scheme was ignored in nacos server
2020-11-25 20:36:43 +08:00
mark4z f3b5db07c6 add doc for ServerConfig 2020-11-25 00:39:28 +08:00
mark4z 273d26ce37 verify whether ipaddr starts with http:// or https:// 2020-11-24 11:10:59 +08:00
lzp0412 2e22680a0a Merge remote-tracking branch 'origin/master'
# Conflicts:
#	example/config/main.go
#	example/service/main.go
#	vo/config_param.go
2020-11-20 18:01:37 +08:00
lzp0412 30d7c1a6d9 Merge branch 'master' of /Users/lizhipeng/git/go/nacos-sdk-go with conflicts. 2020-11-20 18:01:30 +08:00
mark4z 8b26a678f7 some spell error 2020-11-19 21:43:36 +08:00
mark4z 89d796795d add ut for nacosServer.getAddress 2020-11-19 21:41:44 +08:00
mark4z 004d46b7eb Merge remote-tracking branch 'origin/master' 2020-11-19 21:36:22 +08:00
mark4z 7cbab7c3e6 add ut to Server config with scheme 2020-11-19 18:17:28 +08:00
mark4z 1d8110993c add ut to Server config with scheme 2020-11-19 18:14:46 +08:00
mark4z 5abe1a5ccd add ut to Server config with scheme 2020-11-19 18:08:29 +08:00
mark4z ef24495518 add ut to getAddress 2020-11-19 17:57:56 +08:00
mark4z 5e2c6692c0 Revert "test ci"
This reverts commit bc1ebc1c
2020-11-19 16:33:29 +08:00
mark4z bc1ebc1c49 test ci 2020-11-19 16:22:59 +08:00
mark4z 94254e9306 fix ServerConfig scheme was ignored in nacos server 2020-11-19 15:52:57 +08:00
Mark4z fb45465888
Fix go client is not show in the service subscription list (#142)
* fix subscribe fail
2020-10-25 18:04:26 +08:00
mark4z cddb3c114e fix subscribe fail 2020-10-24 22:54:33 +08:00
mark4z 714d03a5d8 fix subscribe fail 2020-10-24 13:57:45 +08:00
mark4z 69797dd7e6 fix subscribe fail 2020-10-24 13:37:11 +08:00
sanxun0325 4d437b437a
Listening configuration increases the namespace
Listening configuration increases the namespace
2020-10-15 13:59:50 +08:00
sanxun0325 28f2dda3b3
Merge pull request #133 from mark4z/fix_post_method
encode params before post method
2020-10-15 13:58:00 +08:00
mark42 e30bf7e36e encode params before post method 2020-10-13 22:25:40 +08:00
sanxun0325 023e4842b6 Listening configuration increases the namespace 2020-10-13 11:16:58 +08:00
sanxun0325 6c8cd18925
Merge pull request #125 from sanxun0325/fix_config_client_cachemap
[ISSUE #123]Fix config client cachemap initialization logic
2020-10-12 15:46:52 +08:00
sanxun0325 ab2276430a fix ut 2020-10-03 12:44:01 +08:00
sanxun0325 f4fed90b9a Merge branch 'master' into fix_config_client_cachemap 2020-10-03 12:19:20 +08:00
sanxun0325 26e692c2a5
Merge pull request #10 from nacos-group/master
sync
2020-10-03 12:18:41 +08:00
sanxun0325 9b9613cdba commit 2020-10-03 11:16:51 +08:00
sanxun0325 170bb4ee01
Merge pull request #118 from sanxun0325/test_github_action
fix ci
2020-09-27 21:50:56 +08:00
sanxun0325 3dbdc5d371 fix ci 2020-09-26 22:36:27 +08:00
sanxun0325 5a49286c2c fix ci 2020-09-26 22:29:21 +08:00
sanxun0325 2420d1517f fix ci 2020-09-26 21:52:30 +08:00
sanxun0325 bd278571b4 fix ci 2020-09-26 21:34:52 +08:00
lzp0412 26061f19c5
Merge pull request #116 from sanxun0325/test_github_action
Test GitHub action
2020-09-26 15:47:11 +08:00
sanxun0325 5619551919 update 2020-09-24 14:08:04 +08:00
sanxun0325 26ecf00c86 update 2020-09-24 13:29:36 +08:00
sanxun0325 ce5ee34bb6 update config_client_test dataid 2020-09-24 13:26:30 +08:00
sanxun0325 40d014bd64 update go.yml 2020-09-21 11:36:49 +08:00
sanxun0325 2546dca9f9 update go.yml 2020-09-21 10:38:55 +08:00
sanxun0325 8d127f4dce update go.yml 2020-09-21 10:26:28 +08:00
sanxun0325 60fe2ff074 Merge branch 'master' into test_github_action
# Conflicts:
#	.github/workflows/go.yml
2020-09-21 10:13:37 +08:00
sanxun0325 4cc3dae27f commit go.yml 2020-09-21 10:12:10 +08:00
sanxun0325 96f93fb8f0
Merge pull request #9 from nacos-group/master
sync
2020-09-20 20:58:43 +08:00
sanxun0325 0e679b8f43
Delete go.yml 2020-09-13 17:00:47 +08:00
sanxun0325 f56fe9c0a4 add travis.yml 2020-09-13 16:54:53 +08:00
sanxun0325 2d485186c2 del go.yml 2020-09-13 16:52:07 +08:00
sanxun0325 55c3f2b1c8 update go.yml 2020-09-13 16:50:02 +08:00
sanxun0325 0e5b8be112 Merge branch 'master' into test_github_action 2020-09-13 16:49:35 +08:00
sanxun0325 2fdd835844
Merge pull request #8 from nacos-group/master
sync
2020-09-13 16:46:19 +08:00
sanxun0325 2d7a64eb97 del travis.yml 2020-09-13 16:37:38 +08:00
sanxun0325 5a2bd14054
Create go.yml 2020-09-13 16:34:31 +08:00
sanxun0325 7d28592002
Merge pull request #113 from holdno/master
登录支持https,账号密码通过body传输
2020-09-13 16:30:46 +08:00
sanxun0325 9ad615eb43
Merge pull request #115 from sanxun0325/listen_config_optimization_md5
Listen config optimization md5
2020-09-13 16:21:51 +08:00
sanxun0325 716d580dfc update ut 2020-09-13 16:15:41 +08:00
sanxun0325 7278ce61e3 commit 2020-09-13 15:14:53 +08:00
sanxun0325 28071edf80
Merge pull request #7 from nacos-group/master
sync
2020-09-13 15:01:54 +08:00
boyan a4ee844804 登录支持https,账号密码通过body传输 2020-09-09 09:38:59 +08:00
lzp0412 b213a56ace
Merge pull request #110 from sanxun0325/remove_registerInstanceParam_tenant
[#109]Remove register instance param tenant
2020-09-02 09:41:23 +08:00
sanxun0325 1106da5ed1 delete tenant 2020-09-02 09:21:16 +08:00
sanxun0325 734a33e689
Merge pull request #6 from nacos-group/master
sync
2020-09-02 09:13:31 +08:00
lzp0412 06957b15e8
Merge pull request #102 from Howie66/master
Fix: Format readme.md and README_CN.md
2020-08-11 14:22:39 +08:00
lzp0412 21ec146cbc
Merge pull request #105 from nacos-group/develop
Develop to master
2020-08-11 14:21:47 +08:00
lzp0412 4116848cd9
Merge pull request #104 from lzp0412/develop
add preservation of copyright and license notices
2020-08-11 14:09:01 +08:00
lzp0412 c7c8e777f0 add preservation of copyright and license notices 2020-08-11 14:04:58 +08:00
howie a3e5445133 Fix: Format readme.md and README_CN.md 2020-08-07 00:23:43 +08:00
lzp0412 d666cbb3b0
Merge pull request #101 from ShortcutEquation/master
FEA(star.xie)nacos-sdk-go/common/:getconfig or putconfig 请求适配协议头https
2020-08-05 19:09:35 +08:00
star.xie ea4adfc80d add https request protocol 2020-08-05 14:41:47 +08:00
star.xie 730a82a0c5 add https request protocol 2020-08-05 14:23:14 +08:00
lzp0412 7f2d503c7f
Merge pull request #99 from nacos-group/develop
develop to master
2020-08-03 00:22:11 +08:00
lzp0412 1dba30d6ae
Merge pull request #98 from sanxun0325/fix_readme
Fix readme
2020-08-03 00:13:43 +08:00
sanxun0325 227cc388f8 fix readme 2020-08-02 20:11:26 +08:00
sanxun0325 5d6c4c2fd1
Merge pull request #5 from nacos-group/master
sync
2020-08-02 19:39:31 +08:00
lzp0412 cc03edb1d8
Merge pull request #97 from nacos-group/develop
develop to master
2020-07-31 23:47:08 +08:00
lzp0412 eb6c601c91
Merge pull request #95 from lzp0412/develop
New Logger implementation and add license
2020-07-31 23:42:54 +08:00
lzp0412 767eabe3f6 add execute delay when error 2020-07-31 23:40:01 +08:00
lzp0412 7ebeabbaac Merge remote-tracking branch 'remotes/origin/develop' into develop-lzp
# Conflicts:
#	clients/config_client/config_client.go
2020-07-31 23:23:29 +08:00
lzp0412 fd4c74755c
Merge pull request #92 from tengzbiao/develop
优化longPulling err时delay、防止日志瞬间写满
2020-07-31 23:13:56 +08:00
lzp0412 642212e5bd fix review comments 2020-07-31 23:12:09 +08:00
lzp0412 96df6ed621 fix review comments 2020-07-30 17:54:06 +08:00
lzp0412 6530060a36 fix review comments 2020-07-30 17:46:50 +08:00
lzp0412 d662701d62 add document 2020-07-30 17:27:39 +08:00
lzp0412 953c2e844e Merge remote-tracking branch 'lzp0412/develop' into develop-lzp 2020-07-30 12:06:57 +08:00
lzp0412 8562fa7413 add copyright license headers 2020-07-30 12:06:43 +08:00
lzp0412 b1df77f457
Merge pull request #4 from nacos-group/develop
Develop
2020-07-30 12:06:09 +08:00
lzp0412 226e7ed44b 1、change logger implementation
2、give some example
3、fix some bug
2020-07-30 11:38:58 +08:00
lzp0412 ffe5a0bb7a
Merge pull request #94 from nacos-group/add-license-2
Create LICENSE
2020-07-30 10:35:57 +08:00
lzp0412 65a96c7e4b
Create LICENSE 2020-07-30 10:10:17 +08:00
tengzbiao 3ae5e666aa 优化longPulling err时delay、防止日志瞬间写满 2020-07-27 21:46:53 +08:00
lzp0412 ff9933ae4b
Merge pull request #3 from nacos-group/develop
Develop
2020-07-26 10:55:50 +08:00
lzp0412 7a3fc3e700
Merge pull request #90 from sanxun0325/getServiceInfo_Concurrent
[ISSUE #63]Get service info concurrent
2020-07-24 19:28:53 +08:00
lzp0412 56869accb2
Merge pull request #89 from sanxun0325/optimize_listen_config
[WIP] Optimize listen config
2020-07-24 19:26:41 +08:00
sanxun0325 c7e56d7cc1 update config_client 2020-07-22 21:26:30 +08:00
sanxun0325 810366017d update readme 2020-07-20 21:01:12 +08:00
sanxun0325 bd4a55004f update host_reator cancel the lock 2020-07-20 20:55:00 +08:00
sanxun0325 2c1a8a98b4 update host_reator 2020-07-18 20:21:11 +08:00
sanxun0325 6cf8caa60c update CancelDelayScheduler 2020-07-18 19:07:45 +08:00
sanxun0325 37dc8eefd4 update get service info concurrent 2020-07-18 11:21:44 +08:00
sanxun0325 466bb52258 Merge branch 'develop' into getServiceInfo_concurrence 2020-07-18 10:20:24 +08:00
sanxun0325 be88d2fa7b update TestDelayScheduler 2020-07-18 10:04:14 +08:00
sanxun0325 85cac3dd95 update example 2020-07-18 09:14:34 +08:00
sanxun0325 c4b4ed39dd update file_test 2020-07-17 08:13:27 +08:00
sanxun0325 a02e6c2273 update config_client 2020-07-16 23:05:56 +08:00
sanxun0325 3a360a4478 Merge branch 'develop' into optimize_listen_config
# Conflicts:
#	clients/config_client/config_client.go
#	clients/config_client/config_client_test.go
#	vo/config_param.go
2020-07-16 22:44:24 +08:00
sanxun0325 0a5fe4b5a0 commit listen config 2020-07-16 22:38:19 +08:00
sanxun0325 cbb09dec72
Merge pull request #4 from nacos-group/develop
Develop
2020-07-15 22:32:45 +08:00
lzp0412 58658a3823
Merge pull request #88 from lzp0412/develop
add travis config
2020-07-15 17:25:50 +08:00
lzp0412 87ca0c2ddc temp delete unit test 2020-07-15 17:22:39 +08:00
lzp0412 d86d83fe82 remove race when run go test 2020-07-15 17:12:25 +08:00
lzp0412 2ae889ee0a format code and fix unit test bug 2020-07-15 14:01:12 +08:00
sanxun0325 e62a4221ba update config listen 2020-07-14 18:18:30 +08:00
lzp0412 9867b485b0 fix interface return bug 2020-07-14 14:24:15 +08:00
lzp0412 6014b547c3 Merge remote-tracking branch 'remotes/origin/master' into develop-lzp 2020-07-14 14:19:07 +08:00
lzp0412 3a6d4ff418 add traivs config 2020-07-14 14:14:06 +08:00
lzp0412 e0979fe1ad del traivs config 2020-07-14 14:11:04 +08:00
lzp0412 aad2d4ed29
Merge pull request #87 from lzp0412/develop
remove uuid dependencies
2020-07-14 14:08:24 +08:00
lzp0412 91bbaa7c12 add traivs config 2020-07-14 14:06:03 +08:00
lzp0412 fd6b410f10 remove uuid dependencies 2020-07-14 11:17:33 +08:00
lzp0412 65c315bee0
Merge pull request #2 from nacos-group/develop
Develop
2020-07-14 09:31:05 +08:00
lzp0412 a3c7614adc
Merge pull request #85 from sanxun0325/fix_naming_client_test
Fix naming client test
2020-07-13 20:04:41 +08:00
sanxun0325 7f1c960a22 Merge branch 'develop' into fix_naming_client_test
# Conflicts:
#	clients/naming_client/naming_client_test.go
2020-07-13 11:13:58 +08:00
sanxun0325 1f0778cefb update config_client 2020-07-13 11:11:34 +08:00
sanxun0325 eda2e830be
Merge pull request #3 from nacos-group/develop
Develop
2020-07-13 11:11:00 +08:00
lzp0412 d91c75fdd8
Merge pull request #84 from sanxun0325/get_sevice_list
Get sevice list
2020-07-13 11:07:00 +08:00
sanxun0325 73c4e61898 commit listen config 2020-07-13 08:58:44 +08:00
sanxun0325 795b895229 update naming_client_test 2020-07-12 12:23:38 +08:00
sanxun0325 eae89b2b94 Merge branch 'develop' into fix_naming_client_test
# Conflicts:
#	clients/naming_client/naming_client_test.go
2020-07-12 12:22:51 +08:00
sanxun0325 fac5d9068c Merge branch 'develop' into get_sevice_list
# Conflicts:
#	clients/naming_client/naming_client_test.go
2020-07-12 12:20:30 +08:00
sanxun0325 295350f0b7
Merge pull request #2 from nacos-group/develop
Develop
2020-07-12 12:15:55 +08:00
lzp0412 338a0c28e8
Merge pull request #73 from sanxun0325/weight_election
update weight election
2020-07-12 10:58:16 +08:00
sanxun0325 1245b12002 fix beat_reactor_test 2020-07-11 18:40:02 +08:00
sanxun0325 77a50d28fc Merge branch 'get_sevice_list' into fix_naming_client_test 2020-07-11 18:32:58 +08:00
sanxun0325 0d062ab13c update chooser.pick() result name 2020-07-11 18:07:42 +08:00
sanxun0325 ef5b9f878c update getServiceList 2020-07-11 18:01:25 +08:00
sanxun0325 be5c4d211a Merge branch 'master' into fix_naming_client_test 2020-07-11 11:11:47 +08:00
sanxun0325 a5418ea8e9 update selectOneHealthyInstanceResult test 2020-07-10 11:07:10 +08:00
sanxun0325 6e42d7d84f Merge branch 'master' into weight_election
# Conflicts:
#	go.sum
2020-07-10 10:43:52 +08:00
sanxun0325 377242c35e
Merge pull request #1 from nacos-group/master
sync
2020-07-10 10:30:42 +08:00
sanxun0325 40c354ae19 update pick() random 2020-07-10 08:56:30 +08:00
sanxun0325 16bcae97cb fix test 2020-07-10 08:22:27 +08:00
lzp0412 ad484435d7
Merge pull request #72 from sanxun0325/cancel_listen
add cancel listen config  func
2020-07-09 19:54:12 +08:00
sanxun0325 a0a1e2d134 update GetServiceInfo func 2020-07-09 17:19:10 +08:00
sanxun0325 55fbd32215 update weight election 2020-07-09 12:28:53 +08:00
sanxun0325 6d9a376a78 add cancel listen config func 2020-07-09 09:30:30 +08:00
lzp0412 bf92551fb3
Merge pull request #65 from ou-bing/fix/naming
服务注册时给 Metadata 设置默认值
2020-07-06 11:23:38 +08:00
lzp0412 5a0cf96199
Merge pull request #66 from duffiye/master
fixed spell error
2020-07-06 11:07:14 +08:00
qimanfei ba6aa923f9 fixed spell error 2020-07-01 18:50:38 +08:00
欧冰 a37752cb22 服务注册时给 Metadata 设置默认值 2020-07-01 11:57:09 +08:00
lzp0412 d8b8e15c4b
Merge pull request #60 from pahxgh/master
修复刷新token的bug
2020-06-22 20:38:39 +08:00
peijinghu e5222310cb 修复动态token刷新时间的bug 2020-06-22 08:37:30 +08:00
peijinghu e9745be3ae 修复动态token刷新时间的bug 2020-06-22 08:24:25 +08:00
peijinghu 90bf88535b 修复刷新token的bug 2020-06-19 17:00:03 +08:00
lzp0412 50c7537d6a
Merge pull request #59 from flycash/master
Add data id field
2020-06-17 10:30:39 +08:00
Ming Deng 16d86bbd07 Add data id field 2020-06-14 21:50:55 +08:00
lzp0412 fd4d0533b3
Merge pull request #54 from liguozhong/uuid
[升级] satori/go.uuid: 1.2.0 升级到 1.2.1
2020-05-07 20:13:34 +08:00
fuling 1091d69fed [升级] satori/go.uuid: 1.2.0 升级到 1.2.1,修复 https://github.com/satori/go.uuid/issues/106 这个问题
Signed-off-by: fuling <fuling.lgz@alibaba-inc.com>
2020-05-07 17:44:54 +08:00
lzp0412 c878b82ba7
Merge pull request #51 from lzp0412/feature-search
fix listen config not notify change when use security
2020-04-19 20:40:18 +08:00
lizhipeng 2c040b8c6d fix listen config not notify change when use security 2020-04-19 20:39:16 +08:00
lzp0412 67407726e4
Merge pull request #50 from lzp0412/feature-search
fix listen config not notify change
2020-04-17 00:07:41 +08:00
lizhipeng ff65a5619a fix listen config not notify change 2020-04-16 23:59:52 +08:00
lzp0412 8b83b6684c
Merge pull request #1 from nacos-group/master
support config search
2020-04-12 17:24:02 +08:00
lzp0412 4b576ae791
Merge pull request #48 from nacos-group/develop
Develop
2020-04-12 17:09:30 +08:00
lzp0412 5940a2b077
Merge pull request #47 from lzp0412/feature-search
Feature search
2020-04-12 17:08:29 +08:00
lizhipeng 322456532b support search config 2020-04-12 17:06:51 +08:00
lzp0412 20301acab7
Merge pull request #45 from nacos-group/develop
Develop
2020-04-12 11:35:36 +08:00
lzp0412 3dc1d05848
Merge pull request #44 from chuntaojun/feature_auth_sys
Feature auth sys
2020-04-12 11:20:08 +08:00
chuntaojun 56ea5a1284 refactor: all
Automatic token refresh logic optimization
2020-04-11 18:47:55 +08:00
lzp0412 0b7233043b
Merge pull request #33 from daixijun/patch-1
Update config_client.go
2020-04-11 18:31:52 +08:00
lzp0412 4a47febb23
Merge pull request #38 from feikiss/fix_exit_bug
It is weird and dangerous to invoke the os.Exit() as a Middleware.
2020-04-11 18:30:52 +08:00
lzp0412 46ce770d57
Merge pull request #43 from caorong/master
fix: update service before subscribe callback
2020-04-11 18:30:19 +08:00
chuntaojun c1a648d283 refactor: remove unuse dir 2020-03-10 14:44:04 +08:00
chuntaojun 9d1323a0d1 feat: supoort nacos 1.2.0 auth sys 2020-03-10 14:43:22 +08:00
caorong ffce007a59 fix: update service before subscribe callback 2020-03-07 21:07:48 +08:00
chuntaojun 1765780da6 feat: support nacos 1.2.0-beta.1 2020-03-02 23:54:10 +08:00
chuntaojun ebf083f3e9 Merge branch 'develop' of https://github.com/nacos-group/nacos-sdk-go into develop 2020-02-27 13:37:29 +08:00
李小飞 9a22c67395 It is weird dangerous to invoke the os.Exit() as a Middleware. 2020-01-06 23:20:59 +08:00
lzp0412 fe1b325b12
Merge pull request #34 from nacos-group/develop
Develop
2019-11-28 16:25:42 +08:00
lzp0412 93f145163b fix not callback when empty host 2019-11-28 16:23:58 +08:00
lzp0412 3b61ce69b3 1、del header of Accept-Encoding
2、remove beat first when deregister
2019-11-28 16:00:12 +08:00
Xijun Dai cae4499818
Update config_client.go 2019-11-25 21:09:21 +08:00
TsingLiang b059fa8ded
Merge pull request #29 from nacos-group/fix_encode
change GBK encode to UTF8
2019-09-06 12:02:52 +08:00
qingliang.ql 341c7cc23e change GBK encode to UTF8 2019-09-06 11:36:42 +08:00
lzp0412 5245ea3cde
Merge pull request #23 from nacos-group/develop
modify readme
2019-08-20 19:24:54 +08:00
lzp0412 c2416b0690 modify readme 2019-08-20 19:24:42 +08:00
lzp0412 77b8c7a712
Merge pull request #22 from nacos-group/develop
ListenInterval should larger than TimeoutMs
2019-08-20 13:08:41 +08:00
lzp0412 f1964b1097 ListenInterval should larger than TimeoutMs 2019-08-20 13:07:09 +08:00
lzp0412 0242d42e3d
Merge pull request #21 from nacos-group/develop
fix remove beatinfo failed bug
2019-07-23 20:54:07 +08:00
lzp0412 fca66a8c24 fix remove beatinfo failed bug 2019-07-23 20:53:27 +08:00
lzp0412 4474393bd5
Merge pull request #20 from nacos-group/develop
fix callback metadata nil bug
2019-07-23 20:35:23 +08:00
lzp0412 d554f4fb43 fix callback metadata nil bug 2019-07-23 20:34:34 +08:00
445 changed files with 16580 additions and 44166 deletions

21
.github/ISSUE_TEMPLATE/bug.md vendored Normal file
View File

@ -0,0 +1,21 @@
---
name: Bug Report
about: Report a bug.
labels: 'Type: Bug'
---
### What version of nacos-sdk-go are you using?
### What version of nacos-sever are you using?
### What version of Go are you using (`go version`)?
### What operating system (Linux, Windows, …) and version?
### What did you do?
If possible, provide a recipe for reproducing the error.
### What did you expect to see?
### What did you see instead?

14
.github/ISSUE_TEMPLATE/feature.md vendored Normal file
View File

@ -0,0 +1,14 @@
---
name: Feature Request
about: Suggest an idea for nacos-sdk-go
labels: 'Type: Feature'
---
### Use case(s) - what problem will this feature solve?
### Proposed Solution
### Alternatives Considered
### Additional Context

7
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,7 @@
---
name: Question
about: Ask a question about nacos-sdk-go
labels: 'Type: Question'
---

38
.github/workflows/go.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: CI
on:
push:
branches: "master"
pull_request:
branches: "*"
jobs:
build:
name: ubuntu-latest ${{ matrix.config.go_version }}
runs-on: ubuntu-latest
strategy:
matrix:
config:
- go_version: 1.21
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.config.go_version }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Test
run: |
diff -u <(echo -n) <(gofmt -d -s .)
diff -u <(echo -n) <(goimports -d .)
go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
- name: Coverage
run: bash <(curl -s https://codecov.io/bash)

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.idea
.data

10
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,10 @@
### Contribution flow
This is a rough outline of what a contributor's workflow looks like:
* Fork the current repository
* Create a topic branch from where to base the contribution. This is usually develop.
* Make commits of logical units.
* Make sure commit messages are in the proper format (see below).
* Push changes in a topic branch to your forked repository.
* Before you sending out the pull request, please sync your forked repository with remote repository, this will make your pull request simple and clear. See guide below:

121
Gopkg.lock generated
View File

@ -1,121 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/aliyun/alibaba-cloud-sdk-go"
packages = [
"sdk",
"sdk/auth",
"sdk/auth/credentials",
"sdk/auth/credentials/provider",
"sdk/auth/signers",
"sdk/endpoints",
"sdk/errors",
"sdk/requests",
"sdk/responses",
"sdk/utils",
"services/kms"
]
revision = "e6958b522d6f315a3585561bca1f87f6a41fd0c3"
[[projects]]
branch = "master"
name = "github.com/buger/jsonparser"
packages = ["."]
revision = "bf1c66bbce23153d89b23f8960071a680dbef54b"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
name = "github.com/go-errors/errors"
packages = ["."]
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
version = "v1.0.1"
[[projects]]
name = "github.com/golang/mock"
packages = ["gomock"]
revision = "9fa652df1129bef0e734c9cf9bf6dbae9ef3b9fa"
version = "1.3.1"
[[projects]]
name = "github.com/jmespath/go-jmespath"
packages = ["."]
revision = "c2b33e84"
[[projects]]
name = "github.com/json-iterator/go"
packages = ["."]
revision = "0ff49de124c6f76f8494e194af75bde0f1a49a29"
version = "v1.1.6"
[[projects]]
branch = "master"
name = "github.com/lestrrat/go-file-rotatelogs"
packages = ["."]
revision = "d3151e2a480fdcd05fb97102f5310a47d96274c4"
[[projects]]
branch = "master"
name = "github.com/lestrrat/go-strftime"
packages = ["."]
revision = "ba3bf9c1d0421aa146564a632931730344f1f9f1"
[[projects]]
name = "github.com/modern-go/concurrent"
packages = ["."]
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
version = "1.0.3"
[[projects]]
name = "github.com/modern-go/reflect2"
packages = ["."]
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
version = "1.0.1"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
version = "v0.8.1"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]]
branch = "master"
name = "github.com/toolkits/concurrent"
packages = ["semaphore"]
revision = "a4371d70e3e308db0d5f90c2b1f5efaa84add725"
[[projects]]
name = "gopkg.in/ini.v1"
packages = ["."]
revision = "8659100d2d9ecf3760a41838b1886db49426d001"
version = "v1.44.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "5f9193892ff6610740b5d7993fe17737c26a52df0ed97a9ed0d4b662aa71455e"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,58 +0,0 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/buger/jsonparser"
[[constraint]]
name = "github.com/go-errors/errors"
version = "1.0.1"
[[constraint]]
name = "github.com/golang/mock"
version = "1.3.1"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.1"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.3.0"
[[constraint]]
branch = "master"
name = "github.com/toolkits/concurrent"
[[constraint]]
name = "github.com/aliyun/alibaba-cloud-sdk-go"
revision = "e6958b522d6f315a3585561bca1f87f6a41fd0c3"
[prune]
go-tests = true
unused-packages = true

22
NOTICE Normal file
View File

@ -0,0 +1,22 @@
Nacos-sdk-go
Copyright 2018-2020
This product includes software developed at
The Alibaba MiddleWare Group.
The concurrent-map project
=============
This product contains code from the concurrent-map project:
Please visit the concurrent-map github web site for more information:
https://github.com/orcaman/concurrent-map
Copyright (c) 2014 streamrail
The go.uuid project
=============
This product contains code from the go.uuid project:
Please visit the go.uuid github web site for more information:
https://github.com/satori/go.uuid
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>

471
README.md
View File

@ -1,226 +1,411 @@
## nacos-go
go语言版本的nacos client支持服务发现和配置管理
# Nacos-sdk-go [中文](./README_CN.md) #
### 客户端配置
[![Build Status](https://travis-ci.org/nacos-group/nacos-sdk-go.svg?branch=master)](https://travis-ci.org/nacos-group/nacos-sdk-go) [![Go Report Card](https://goreportcard.com/badge/github.com/nacos-group/nacos-sdk-go)](https://goreportcard.com/report/github.com/nacos-group/nacos-sdk-go) ![license](https://img.shields.io/badge/license-Apache--2.0-green.svg)
* ClientConfig 客户端配置参数
```go
constant.ClientConfig{
TimeoutMs: 30 * 1000, //http请求超时时间单位毫秒
ListenInterval: 10 * 1000, //监听间隔时间单位毫秒仅在ConfigClient中有效
BeatInterval: 5 * 1000, //心跳间隔时间单位毫秒仅在ServiceClient中有效
NamespaceId: "public", //nacos命名空间
Endpoint: "" //获取nacos节点ip的服务地址
CacheDir: "/data/nacos/cache", //缓存目录
LogDIr: "/data/nacos/log", //日志目录
UpdateThreadNum: 20, //更新服务的线程数
NotLoadCacheAtStart: true, //在启动时不读取本地缓存数据true--不读取false--读取
UpdateCacheWhenEmpty: true, //当服务列表为空时是否更新本地缓存true--更新,false--不更新
}
---
## Nacos-sdk-go
Nacos-sdk-go for Go client allows you to access Nacos service,it supports service discovery and dynamic configuration.
## Requirements
Supported Go version over 1.15
Supported Nacos version over 2.x
## Installation
Use `go get` to install SDK
```sh
$ go get -u github.com/nacos-group/nacos-sdk-go/v2
```
## Quick Examples
* ServerConfig nacos服务信息配置参数
* ClientConfig
```go
constant.ClientConfig {
TimeoutMs uint64 // timeout for requesting Nacos server, default value is 10000ms
NamespaceId string // the namespaceId of Nacos
Endpoint string // the endpoint for ACM. https://help.aliyun.com/document_detail/130146.html
RegionId string // the regionId for ACM & KMS
AccessKey string // the AccessKey for ACM & KMS
SecretKey string // the SecretKey for ACM & KMS
OpenKMS bool // it's to open KMS, default is false. https://help.aliyun.com/product/28933.html
// , to enable encrypt/decrypt, DataId should be start with "cipher-"
CacheDir string // the directory for persist nacos service info,default value is current path
UpdateThreadNum int // the number of goroutine for update nacos service info,default value is 20
NotLoadCacheAtStart bool // not to load persistent nacos service info in CacheDir at start time
UpdateCacheWhenEmpty bool // update cache when get empty service instance from server
Username string // the username for nacos auth
Password string // the password for nacos auth
LogDir string // the directory for log, default is current path
RotateTime string // the rotate time for log, eg: 30m, 1h, 24h, default is 24h
MaxAge int64 // the max age of a log file, default value is 3
LogLevel string // the level of log, it's must be debug,info,warn,error, default value is info
}
```
* ServerConfig
```go
constant.ServerConfig{
IpAddr: "console.nacos.io", //nacos服务的ip地址
ContextPath: "/nacos", //nacos服务的上下文路径默认是“/nacos”
Port: 80, //nacos服务端口
}
```
<b>ServerConfig支持配置多个在请求出错时自动切换</b>
### 构造客户端
```go
// 可以没有,采用默认值
clientConfig := constant.ClientConfig{
TimeoutMs: 30 * 1000,
ListenInterval: 10 * 1000,
BeatInterval: 5 * 1000,
LogDir: "/nacos/logs",
CacheDir: "/nacos/cache",
}
// 至少一个
serverConfigs := []constant.ServerConfig{
{
IpAddr: "console1.nacos.io",
ContextPath: "/nacos",
Port: 80,
},
{
IpAddr: "console2.nacos.io",
ContextPath: "/nacos",
Port: 80,
},
Scheme string // the nacos server scheme,defaut=http,this is not required in 2.0
ContextPath string // the nacos server contextpath,defaut=/nacos,this is not required in 2.0
IpAddr string // the nacos server address
Port uint64 // nacos server port
GrpcPort uint64 // nacos server grpc port, default=server port + 1000, this is not required
}
namingClient, err := clients.CreateNamingClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
configClient, err := clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
```
<b>NoteWe can config multiple ServerConfig,the client will rotate request the servers</b>
### 服务发现
* 注册服务实例RegisterInstance
### Create client
```go
success, _ := namingClient.RegisterInstance(vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
ClusterName: "a",
Enable: true,
Healthy: true,
Ephemeral: true,
})
//create clientConfig
clientConfig := constant.ClientConfig{
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", //we can create multiple clients with different namespaceId to support multiple namespace.When namespace is public, fill in the blank string here.
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
LogLevel: "debug",
}
//Another way of create clientConfig
clientConfig := *constant.NewClientConfig(
constant.WithNamespaceId("e525eafa-f7d7-4029-83d9-008937f9d468"), //When namespace is public, fill in the blank string here.
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
constant.WithCacheDir("/tmp/nacos/cache"),
constant.WithLogLevel("debug"),
)
// At least one ServerConfig
serverConfigs := []constant.ServerConfig{
{
IpAddr: "console1.nacos.io",
ContextPath: "/nacos",
Port: 80,
Scheme: "http",
},
{
IpAddr: "console2.nacos.io",
ContextPath: "/nacos",
Port: 80,
Scheme: "http",
},
}
//Another way of create serverConfigs
serverConfigs := []constant.ServerConfig{
*constant.NewServerConfig(
"console1.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos")
),
*constant.NewServerConfig(
"console2.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos")
),
}
// Create naming client for service discovery
_, _ := clients.CreateNamingClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
// Create config client for dynamic configuration
_, _ := clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
// Another way of create naming client for service discovery (recommend)
namingClient, err := clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &clientConfig,
ServerConfigs: serverConfigs,
},
)
// Another way of create config client for dynamic configuration (recommend)
configClient, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &clientConfig,
ServerConfigs: serverConfigs,
},
)
```
* 注销服务实例DeregisterInstance
### Create client for ACM
https://help.aliyun.com/document_detail/130146.html
```go
success, _ := namingClient.DeregisterInstance(vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
ClusterName: "a",
Ephemeral: true,
})
cc := constant.ClientConfig{
Endpoint: "acm.aliyun.com:8080",
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468",
RegionId: "cn-shanghai",
AccessKey: "LTAI4G8KxxxxxxxxxxxxxbwZLBr",
SecretKey: "n5jTL9YxxxxxxxxxxxxaxmPLZV9",
OpenKMS: true,
TimeoutMs: 5000,
LogLevel: "debug",
}
// a more graceful way to create config client
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
},
)
```
* 获取服务GetService
### Service Discovery
* Register instanceRegisterInstance
```go
service, _ := namingClient.GetService(vo.GetServiceParam{
ServiceName: "demo.go",
Clusters: []string{"a"},
})
success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc":"shanghai"},
ClusterName: "cluster-a", // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
```
* 获取所有的实例列表SelectAllInstances
* Deregister instanceDeregisterInstance
```go
instances, err := namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
ServiceName: "demo.go",
Clusters: []string{"a"},
})
success, err := namingClient.DeregisterInstance(vo.DeregisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Ephemeral: true,
Cluster: "cluster-a", // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
```
* 获取实例列表SelectInstances
* Get serviceGetService
```go
instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
ServiceName: "demo.go",
Clusters: []string{"a"},
HealthyOnly: true,
})
services, err := namingClient.GetService(vo.GetServiceParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-a"}, // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
```
* 获取一个健康的实例加权轮训负载均衡SelectOneHealthyInstance
* Get all instancesSelectAllInstances
```go
instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{
ServiceName: "demo.go",
Clusters: []string{"a"},
})
// SelectAllInstance return all instances,include healthy=false,enable=false,weight<=0
instances, err := namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
})
```
* 服务监听Subscribe
* Get instances SelectInstances
```go
namingClient.Subscribe(vo.SubscribeParam{
ServiceName: "demo.go",
Clusters: []string{"a"},
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
// SelectInstances only return the instances of healthy=${HealthyOnly},enable=true and weight>0
instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
HealthyOnly: true,
})
```
* 取消服务监听Unsubscribe
* Get one healthy instanceWRRSelectOneHealthyInstance
```go
// SelectOneHealthyInstance return one instance by WRR strategy for load balance
// And the instance should be health=true,enable=true and weight>0
instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
})
```
* Listen service change eventSubscribe
```go
namingClient.Unsubscribe(vo.SubscribeParam{
ServiceName: "demo.go",
Clusters: []string{"a"},
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
// Subscribe key = serviceName+groupName+cluster
// Note: We call add multiple SubscribeCallback with the same key.
err := namingClient.Subscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
SubscribeCallback: func (services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
```
### 配置管理
* Cancel listen of service change eventUnsubscribe
* 发布配置PublishConfig
```go
err := namingClient.Unsubscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
SubscribeCallback: func (services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
```
* Get all services name:GetAllServicesInfo
```go
serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,
}),
```
### Dynamic configuration
* publish configPublishConfig
```go
success, err := configClient.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
```
* 删除配置DeleteConfig
* delete configDeleteConfig
```go
success, err = configClient.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: "dataId",
Group: "group"})
```
* 获取配置GetConfig
* get config infoGetConfig
```go
content, err := configClient.GetConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: "dataId",
Group: "group"})
```
* 监听配置ListenConfig
* Listen config change eventListenConfig
```go
configClient.ListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
},
})
err := configClient.ListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
OnChange: func (namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
},
})
```
* Cancel the listening of config change eventCancelListenConfig
```go
err := configClient.CancelListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
})
```
* Search config: SearchConfig
```go
configPage, err := configClient.SearchConfig(vo.SearchConfigParam{
Search: "blur",
DataId: "",
Group: "",
PageNo: 1,
PageSize: 10,
})
```
## Example
We can run example to learn how to use nacos go client.
* [Config Example](./example/config)
* [Naming Example](./example/service)
## Documentation
You can view the open-api documentation from the [Nacos open-api wepsite](https://nacos.io/en-us/docs/open-api.html).
You can view the full documentation from the [Nacos website](https://nacos.io/en-us/docs/what-is-nacos.html).
## Contributing
Contributors are welcomed to join Nacos-sdk-go project. Please check [CONTRIBUTING.md](./CONTRIBUTING.md) about how to
contribute to this project.
## Contact
* Join us from DingDing Group(23191211).
* [Gitter](https://gitter.im/alibaba/nacos): Nacos's IM tool for community messaging, collaboration and discovery.
* [Twitter](https://twitter.com/nacos2): Follow along for latest nacos news on Twitter.
* [Weibo](https://weibo.com/u/6574374908): Follow along for latest nacos news on Weibo (Twitter of China version).
* [Nacos SegmentFault](https://segmentfault.com/t/nacos): Get the latest notice and prompt help from SegmentFault.
* Email Group:
* users-nacos@googlegroups.com: Nacos usage general discussion.
* dev-nacos@googlegroups.com: Nacos developer discussion (APIs, feature design, etc).
* commits-nacos@googlegroups.com: Commits notice, very high frequency.
```

389
README_CN.md Normal file
View File

@ -0,0 +1,389 @@
# Nacos-sdk-go [English](./README.md) #
[![Build Status](https://travis-ci.org/nacos-group/nacos-sdk-go.svg?branch=master)](https://travis-ci.org/nacos-group/nacos-sdk-go) [![Go Report Card](https://goreportcard.com/badge/github.com/nacos-group/nacos-sdk-go)](https://goreportcard.com/report/github.com/nacos-group/nacos-sdk-go) ![license](https://img.shields.io/badge/license-Apache--2.0-green.svg)
---
## Nacos-sdk-go
Nacos-sdk-go是Nacos的Go语言客户端它实现了服务发现和动态配置的功能
## 使用限制
支持Go>=v1.15版本
支持Nacos>2.x版本
## 安装
使用`go get`安装SDK
```sh
$ go get -u github.com/nacos-group/nacos-sdk-go/v2
```
## 快速使用
* ClientConfig
```go
constant.ClientConfig{
TimeoutMs uint64 // 请求Nacos服务端的超时时间默认是10000ms
NamespaceId string // ACM的命名空间Id
Endpoint string // 当使用ACM时需要该配置. https://help.aliyun.com/document_detail/130146.html
RegionId string // ACM&KMS的regionId用于配置中心的鉴权
AccessKey string // ACM&KMS的AccessKey用于配置中心的鉴权
SecretKey string // ACM&KMS的SecretKey用于配置中心的鉴权
OpenKMS bool // 是否开启kms默认不开启kms可以参考文档 https://help.aliyun.com/product/28933.html
// 同时DataId必须以"cipher-"作为前缀才会启动加解密逻辑
CacheDir string // 缓存service信息的目录默认是当前运行目录
UpdateThreadNum int // 监听service变化的并发数默认20
NotLoadCacheAtStart bool // 在启动的时候不读取缓存在CacheDir的service信息
UpdateCacheWhenEmpty bool // 当service返回的实例列表为空时不更新缓存用于推空保护
Username string // Nacos服务端的API鉴权Username
Password string // Nacos服务端的API鉴权Password
LogDir string // 日志存储路径
RotateTime string // 日志轮转周期比如30m, 1h, 24h, 默认是24h
MaxAge int64 // 日志最大文件数默认3
LogLevel string // 日志默认级别值必须是debug,info,warn,error默认值是info
}
```
* ServerConfig
```go
constant.ServerConfig{
ContextPath string // Nacos的ContextPath默认/nacos在2.0中不需要设置
IpAddr string // Nacos的服务地址
Port uint64 // Nacos的服务端口
Scheme string // Nacos的服务地址前缀默认http在2.0中不需要设置
GrpcPort uint64 // Nacos的 grpc 服务端口, 默认为 服务端口+1000, 不是必填
}
```
<b>Note我们可以配置多个ServerConfig客户端会对这些服务端做轮询请求</b>
### Create client
```go
// 创建clientConfig
clientConfig := constant.ClientConfig{
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", // 如果需要支持多namespace我们可以创建多个client,它们有不同的NamespaceId。当namespace是public时此处填空字符串。
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
LogLevel: "debug",
}
// 创建clientConfig的另一种方式
clientConfig := *constant.NewClientConfig(
constant.WithNamespaceId("e525eafa-f7d7-4029-83d9-008937f9d468"), //当namespace是public时此处填空字符串。
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
constant.WithCacheDir("/tmp/nacos/cache"),
constant.WithLogLevel("debug"),
)
// 至少一个ServerConfig
serverConfigs := []constant.ServerConfig{
{
IpAddr: "console1.nacos.io",
ContextPath: "/nacos",
Port: 80,
Scheme: "http",
},
{
IpAddr: "console2.nacos.io",
ContextPath: "/nacos",
Port: 80,
Scheme: "http",
},
}
// 创建serverConfig的另一种方式
serverConfigs := []constant.ServerConfig{
*constant.NewServerConfig(
"console1.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos"),
),
*constant.NewServerConfig(
"console2.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos"),
),
}
// 创建服务发现客户端
_, _ := clients.CreateNamingClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
// 创建动态配置客户端
_, _ := clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
// 创建服务发现客户端的另一种方式 (推荐)
namingClient, err := clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &clientConfig,
ServerConfigs: serverConfigs,
},
)
// 创建动态配置客户端的另一种方式 (推荐)
configClient, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &clientConfig,
ServerConfigs: serverConfigs,
},
)
```
### Create client for ACM
https://help.aliyun.com/document_detail/130146.html
```go
cc := constant.ClientConfig{
Endpoint: "acm.aliyun.com:8080",
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468",
RegionId: "cn-shanghai",
AccessKey: "LTAI4G8KxxxxxxxxxxxxxbwZLBr",
SecretKey: "n5jTL9YxxxxxxxxxxxxaxmPLZV9",
OpenKMS: true,
TimeoutMs: 5000,
LogLevel: "debug",
}
// a more graceful way to create config client
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
},
)
```
### 服务发现
* 注册实例RegisterInstance
```go
success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc":"shanghai"},
ClusterName: "cluster-a", // 默认值DEFAULT
GroupName: "group-a", // 默认值DEFAULT_GROUP
})
```
* 注销实例DeregisterInstance
```go
success, err := namingClient.DeregisterInstance(vo.DeregisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Ephemeral: true,
Cluster: "cluster-a", // 默认值DEFAULT
GroupName: "group-a", // 默认值DEFAULT_GROUP
})
```
* 获取服务信息GetService
```go
services, err := namingClient.GetService(vo.GetServiceParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
GroupName: "group-a", // 默认值DEFAULT_GROUP
})
```
* 获取所有的实例列表SelectAllInstances
```go
// SelectAllInstance可以返回全部实例列表,包括healthy=false,enable=false,weight<=0
instances, err := namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a", // 默认值DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
})
```
* 获取实例列表 SelectInstances
```go
// SelectInstances 只返回满足这些条件的实例列表healthy=${HealthyOnly},enable=true 和weight>0
instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a", // 默认值DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
HealthyOnly: true,
})
```
* 获取一个健康的实例加权随机轮询SelectOneHealthyInstance
```go
// SelectOneHealthyInstance将会按加权随机轮询的负载均衡策略返回一个健康的实例
// 实例必须满足的条件health=true,enable=true and weight>0
instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanceParam{
ServiceName: "demo.go",
GroupName: "group-a", // 默认值DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
})
```
* 监听服务变化Subscribe
```go
// Subscribe key=serviceName+groupName+cluster
// 注意:我们可以在相同的key添加多个SubscribeCallback.
err := namingClient.Subscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // 默认值DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
```
* 取消服务监听Unsubscribe
```go
err := namingClient.Unsubscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // 默认值DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
```
* 获取服务名列表:GetAllServicesInfo
```go
serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,
}),
```
### 动态配置
* 发布配置PublishConfig
```go
success, err := configClient.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
```
* 删除配置DeleteConfig
```go
success, err = configClient.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
```
* 获取配置GetConfig
```go
content, err := configClient.GetConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
```
* 监听配置变化ListenConfig
```go
err := configClient.ListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
},
})
```
* 取消配置监听CancelListenConfig
```go
err := configClient.CancelListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
})
```
* 搜索配置: SearchConfig
```go
configPage,err := configClient.SearchConfig(vo.SearchConfigParam{
Search: "blur",
DataId: "",
Group: "",
PageNo: 1,
PageSize: 10,
})
```
## 例子
我们能从示例中学习如何使用Nacos go客户端
* [动态配置示例](./example/config)
* [服务发现示例](./example/service)
## 文档
Nacos open-api相关信息可以查看文档 [Nacos open-api wepsite](https://nacos.io/en-us/docs/open-api.html).
Nacos产品了解可以查看 [Nacos website](https://nacos.io/en-us/docs/what-is-nacos.html).
## 贡献代码
我们非常欢迎大家为Nacos-sdk-go贡献代码. 贡献前请查看[CONTRIBUTING.md](./CONTRIBUTING.md)
## 联系我们
* 加入Nacos-sdk-go钉钉群(23191211).
* [Gitter](https://gitter.im/alibaba/nacos): Nacos即时聊天工具.
* [Twitter](https://twitter.com/nacos2): 在Twitter上关注Nacos的最新动态.
* [Weibo](https://weibo.com/u/6574374908): 在微博上关注Nacos的最新动态.
* [Nacos SegmentFault](https://segmentfault.com/t/nacos): SegmentFault可以获得最新的推送和帮助.
* Email Group:
* users-nacos@googlegroups.com: Nacos用户讨论组.
* dev-nacos@googlegroups.com: Nacos开发者讨论组 (APIs, feature design, etc).
* commits-nacos@googlegroups.com: Nacos commit提醒.

View File

@ -0,0 +1,233 @@
//
// Copyright 1999-2020 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.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.7
// protoc v5.29.3
// source: api/proto/nacos_grpc_service.proto
package auto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
anypb "google.golang.org/protobuf/types/known/anypb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Metadata struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
ClientIp string `protobuf:"bytes,8,opt,name=clientIp,proto3" json:"clientIp,omitempty"`
Headers map[string]string `protobuf:"bytes,7,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Metadata) Reset() {
*x = Metadata{}
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Metadata) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Metadata) ProtoMessage() {}
func (x *Metadata) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
func (*Metadata) Descriptor() ([]byte, []int) {
return file_api_proto_nacos_grpc_service_proto_rawDescGZIP(), []int{0}
}
func (x *Metadata) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *Metadata) GetClientIp() string {
if x != nil {
return x.ClientIp
}
return ""
}
func (x *Metadata) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
type Payload struct {
state protoimpl.MessageState `protogen:"open.v1"`
Metadata *Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"`
Body *anypb.Any `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Payload) Reset() {
*x = Payload{}
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Payload) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Payload) ProtoMessage() {}
func (x *Payload) ProtoReflect() protoreflect.Message {
mi := &file_api_proto_nacos_grpc_service_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Payload.ProtoReflect.Descriptor instead.
func (*Payload) Descriptor() ([]byte, []int) {
return file_api_proto_nacos_grpc_service_proto_rawDescGZIP(), []int{1}
}
func (x *Payload) GetMetadata() *Metadata {
if x != nil {
return x.Metadata
}
return nil
}
func (x *Payload) GetBody() *anypb.Any {
if x != nil {
return x.Body
}
return nil
}
var File_api_proto_nacos_grpc_service_proto protoreflect.FileDescriptor
const file_api_proto_nacos_grpc_service_proto_rawDesc = "" +
"\n" +
"\"api/proto/nacos_grpc_service.proto\x1a\x19google/protobuf/any.proto\"\xa8\x01\n" +
"\bMetadata\x12\x12\n" +
"\x04type\x18\x03 \x01(\tR\x04type\x12\x1a\n" +
"\bclientIp\x18\b \x01(\tR\bclientIp\x120\n" +
"\aheaders\x18\a \x03(\v2\x16.Metadata.HeadersEntryR\aheaders\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"Z\n" +
"\aPayload\x12%\n" +
"\bmetadata\x18\x02 \x01(\v2\t.MetadataR\bmetadata\x12(\n" +
"\x04body\x18\x03 \x01(\v2\x14.google.protobuf.AnyR\x04body28\n" +
"\rRequestStream\x12'\n" +
"\rrequestStream\x12\b.Payload\x1a\b.Payload\"\x000\x012*\n" +
"\aRequest\x12\x1f\n" +
"\arequest\x12\b.Payload\x1a\b.Payload\"\x002>\n" +
"\x0fBiRequestStream\x12+\n" +
"\x0frequestBiStream\x12\b.Payload\x1a\b.Payload\"\x00(\x010\x01B^\n" +
"\x1fcom.alibaba.nacos.api.grpc.autoP\x01Z9github.com/nacos-group/nacos-sdk-go/v2/api/grpc/auto;autob\x06proto3"
var (
file_api_proto_nacos_grpc_service_proto_rawDescOnce sync.Once
file_api_proto_nacos_grpc_service_proto_rawDescData []byte
)
func file_api_proto_nacos_grpc_service_proto_rawDescGZIP() []byte {
file_api_proto_nacos_grpc_service_proto_rawDescOnce.Do(func() {
file_api_proto_nacos_grpc_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_nacos_grpc_service_proto_rawDesc), len(file_api_proto_nacos_grpc_service_proto_rawDesc)))
})
return file_api_proto_nacos_grpc_service_proto_rawDescData
}
var file_api_proto_nacos_grpc_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_api_proto_nacos_grpc_service_proto_goTypes = []any{
(*Metadata)(nil), // 0: Metadata
(*Payload)(nil), // 1: Payload
nil, // 2: Metadata.HeadersEntry
(*anypb.Any)(nil), // 3: google.protobuf.Any
}
var file_api_proto_nacos_grpc_service_proto_depIdxs = []int32{
2, // 0: Metadata.headers:type_name -> Metadata.HeadersEntry
0, // 1: Payload.metadata:type_name -> Metadata
3, // 2: Payload.body:type_name -> google.protobuf.Any
1, // 3: RequestStream.requestStream:input_type -> Payload
1, // 4: Request.request:input_type -> Payload
1, // 5: BiRequestStream.requestBiStream:input_type -> Payload
1, // 6: RequestStream.requestStream:output_type -> Payload
1, // 7: Request.request:output_type -> Payload
1, // 8: BiRequestStream.requestBiStream:output_type -> Payload
6, // [6:9] is the sub-list for method output_type
3, // [3:6] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_api_proto_nacos_grpc_service_proto_init() }
func file_api_proto_nacos_grpc_service_proto_init() {
if File_api_proto_nacos_grpc_service_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_nacos_grpc_service_proto_rawDesc), len(file_api_proto_nacos_grpc_service_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 3,
},
GoTypes: file_api_proto_nacos_grpc_service_proto_goTypes,
DependencyIndexes: file_api_proto_nacos_grpc_service_proto_depIdxs,
MessageInfos: file_api_proto_nacos_grpc_service_proto_msgTypes,
}.Build()
File_api_proto_nacos_grpc_service_proto = out.File
file_api_proto_nacos_grpc_service_proto_goTypes = nil
file_api_proto_nacos_grpc_service_proto_depIdxs = nil
}

View File

@ -0,0 +1,343 @@
//
// Copyright 1999-2020 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.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: api/proto/nacos_grpc_service.proto
package auto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
RequestStream_RequestStream_FullMethodName = "/RequestStream/requestStream"
)
// RequestStreamClient is the client API for RequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type RequestStreamClient interface {
// build a streamRequest
RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Payload], error)
}
type requestStreamClient struct {
cc grpc.ClientConnInterface
}
func NewRequestStreamClient(cc grpc.ClientConnInterface) RequestStreamClient {
return &requestStreamClient{cc}
}
func (c *requestStreamClient) RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Payload], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &RequestStream_ServiceDesc.Streams[0], RequestStream_RequestStream_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[Payload, Payload]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type RequestStream_RequestStreamClient = grpc.ServerStreamingClient[Payload]
// RequestStreamServer is the server API for RequestStream service.
// All implementations must embed UnimplementedRequestStreamServer
// for forward compatibility.
type RequestStreamServer interface {
// build a streamRequest
RequestStream(*Payload, grpc.ServerStreamingServer[Payload]) error
mustEmbedUnimplementedRequestStreamServer()
}
// UnimplementedRequestStreamServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedRequestStreamServer struct{}
func (UnimplementedRequestStreamServer) RequestStream(*Payload, grpc.ServerStreamingServer[Payload]) error {
return status.Errorf(codes.Unimplemented, "method RequestStream not implemented")
}
func (UnimplementedRequestStreamServer) mustEmbedUnimplementedRequestStreamServer() {}
func (UnimplementedRequestStreamServer) testEmbeddedByValue() {}
// UnsafeRequestStreamServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to RequestStreamServer will
// result in compilation errors.
type UnsafeRequestStreamServer interface {
mustEmbedUnimplementedRequestStreamServer()
}
func RegisterRequestStreamServer(s grpc.ServiceRegistrar, srv RequestStreamServer) {
// If the following call pancis, it indicates UnimplementedRequestStreamServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&RequestStream_ServiceDesc, srv)
}
func _RequestStream_RequestStream_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(Payload)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(RequestStreamServer).RequestStream(m, &grpc.GenericServerStream[Payload, Payload]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type RequestStream_RequestStreamServer = grpc.ServerStreamingServer[Payload]
// RequestStream_ServiceDesc is the grpc.ServiceDesc for RequestStream service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var RequestStream_ServiceDesc = grpc.ServiceDesc{
ServiceName: "RequestStream",
HandlerType: (*RequestStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "requestStream",
Handler: _RequestStream_RequestStream_Handler,
ServerStreams: true,
},
},
Metadata: "api/proto/nacos_grpc_service.proto",
}
const (
Request_Request_FullMethodName = "/Request/request"
)
// RequestClient is the client API for Request service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type RequestClient interface {
// Sends a commonRequest
Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error)
}
type requestClient struct {
cc grpc.ClientConnInterface
}
func NewRequestClient(cc grpc.ClientConnInterface) RequestClient {
return &requestClient{cc}
}
func (c *requestClient) Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Payload)
err := c.cc.Invoke(ctx, Request_Request_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// RequestServer is the server API for Request service.
// All implementations must embed UnimplementedRequestServer
// for forward compatibility.
type RequestServer interface {
// Sends a commonRequest
Request(context.Context, *Payload) (*Payload, error)
mustEmbedUnimplementedRequestServer()
}
// UnimplementedRequestServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedRequestServer struct{}
func (UnimplementedRequestServer) Request(context.Context, *Payload) (*Payload, error) {
return nil, status.Errorf(codes.Unimplemented, "method Request not implemented")
}
func (UnimplementedRequestServer) mustEmbedUnimplementedRequestServer() {}
func (UnimplementedRequestServer) testEmbeddedByValue() {}
// UnsafeRequestServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to RequestServer will
// result in compilation errors.
type UnsafeRequestServer interface {
mustEmbedUnimplementedRequestServer()
}
func RegisterRequestServer(s grpc.ServiceRegistrar, srv RequestServer) {
// If the following call pancis, it indicates UnimplementedRequestServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Request_ServiceDesc, srv)
}
func _Request_Request_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Payload)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RequestServer).Request(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Request_Request_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RequestServer).Request(ctx, req.(*Payload))
}
return interceptor(ctx, in, info, handler)
}
// Request_ServiceDesc is the grpc.ServiceDesc for Request service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Request_ServiceDesc = grpc.ServiceDesc{
ServiceName: "Request",
HandlerType: (*RequestServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "request",
Handler: _Request_Request_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/proto/nacos_grpc_service.proto",
}
const (
BiRequestStream_RequestBiStream_FullMethodName = "/BiRequestStream/requestBiStream"
)
// BiRequestStreamClient is the client API for BiRequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type BiRequestStreamClient interface {
// Sends a commonRequest
RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Payload, Payload], error)
}
type biRequestStreamClient struct {
cc grpc.ClientConnInterface
}
func NewBiRequestStreamClient(cc grpc.ClientConnInterface) BiRequestStreamClient {
return &biRequestStreamClient{cc}
}
func (c *biRequestStreamClient) RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Payload, Payload], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &BiRequestStream_ServiceDesc.Streams[0], BiRequestStream_RequestBiStream_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[Payload, Payload]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BiRequestStream_RequestBiStreamClient = grpc.BidiStreamingClient[Payload, Payload]
// BiRequestStreamServer is the server API for BiRequestStream service.
// All implementations must embed UnimplementedBiRequestStreamServer
// for forward compatibility.
type BiRequestStreamServer interface {
// Sends a commonRequest
RequestBiStream(grpc.BidiStreamingServer[Payload, Payload]) error
mustEmbedUnimplementedBiRequestStreamServer()
}
// UnimplementedBiRequestStreamServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedBiRequestStreamServer struct{}
func (UnimplementedBiRequestStreamServer) RequestBiStream(grpc.BidiStreamingServer[Payload, Payload]) error {
return status.Errorf(codes.Unimplemented, "method RequestBiStream not implemented")
}
func (UnimplementedBiRequestStreamServer) mustEmbedUnimplementedBiRequestStreamServer() {}
func (UnimplementedBiRequestStreamServer) testEmbeddedByValue() {}
// UnsafeBiRequestStreamServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BiRequestStreamServer will
// result in compilation errors.
type UnsafeBiRequestStreamServer interface {
mustEmbedUnimplementedBiRequestStreamServer()
}
func RegisterBiRequestStreamServer(s grpc.ServiceRegistrar, srv BiRequestStreamServer) {
// If the following call pancis, it indicates UnimplementedBiRequestStreamServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&BiRequestStream_ServiceDesc, srv)
}
func _BiRequestStream_RequestBiStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BiRequestStreamServer).RequestBiStream(&grpc.GenericServerStream[Payload, Payload]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type BiRequestStream_RequestBiStreamServer = grpc.BidiStreamingServer[Payload, Payload]
// BiRequestStream_ServiceDesc is the grpc.ServiceDesc for BiRequestStream service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BiRequestStream_ServiceDesc = grpc.ServiceDesc{
ServiceName: "BiRequestStream",
HandlerType: (*BiRequestStreamServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "requestBiStream",
Handler: _BiRequestStream_RequestBiStream_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "api/proto/nacos_grpc_service.proto",
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 1999-2020 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.
*/
syntax = "proto3";
import "google/protobuf/any.proto";
option java_multiple_files = true;
option java_package = "com.alibaba.nacos.api.grpc.auto";
option go_package = "github.com/nacos-group/nacos-sdk-go/v2/api/grpc/auto;auto";
message Metadata {
string type = 3;
string clientIp = 8;
map<string, string> headers = 7;
}
message Payload {
Metadata metadata = 2;
google.protobuf.Any body = 3;
}
service RequestStream {
// build a streamRequest
rpc requestStream (Payload) returns (stream Payload) {
}
}
service Request {
// Sends a commonRequest
rpc request (Payload) returns (Payload) {
}
}
service BiRequestStream {
// Sends a commonRequest
rpc requestBiStream (stream Payload) returns (stream Payload) {
}
}

View File

@ -1,3 +1,29 @@
/*
The MIT License (MIT)
Copyright (c) 2014 streamrail
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.
*/
package cache
import (
@ -270,7 +296,7 @@ func (m ConcurrentMap) Keys() []string {
return keys
}
//Reviles ConcurrentMap "private" variables to json marshal.
// Reviles ConcurrentMap "private" variables to json marshal.
func (m ConcurrentMap) MarshalJSON() ([]byte, error) {
// Create a temporary map, which will hold all item spread across shards.
tmp := make(map[string]interface{})

11
clients/cache/const.go vendored Normal file
View File

@ -0,0 +1,11 @@
package cache
type ConfigCachedFileType string
const (
ConfigContent ConfigCachedFileType = "Config Content"
ConfigEncryptedDataKey ConfigCachedFileType = "Config Encrypted Data Key"
ENCRYPTED_DATA_KEY_FILE_NAME = "encrypted-data-key"
FAILOVER_FILE_SUFFIX = "_failover"
)

View File

@ -1,77 +1,203 @@
/*
* Copyright 1999-2020 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.
*/
package cache
import (
"encoding/json"
"fmt"
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/common/util"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"syscall"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
)
func GetFileName(cacheKey string, cacheDir string) string {
var (
fileNotExistError = errors.New("file not exist")
)
func GetFileName(cacheKey, cacheDir string) string {
return cacheDir + string(os.PathSeparator) + cacheKey
}
func WriteServicesToFile(service model.Service, cacheDir string) {
util.MkdirIfNecessary(cacheDir)
sb, _ := json.Marshal(service)
domFileName := GetFileName(utils.GetServiceCacheKey(service.Name, service.Clusters), cacheDir)
func GetEncryptedDataKeyDir(cacheDir string) string {
return cacheDir + string(os.PathSeparator) + ENCRYPTED_DATA_KEY_FILE_NAME
}
err := ioutil.WriteFile(domFileName, sb, 0666)
func GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir string) string {
return GetEncryptedDataKeyDir(cacheDir) + string(os.PathSeparator) + cacheKey
}
func GetConfigFailOverContentFileName(cacheKey, cacheDir string) string {
return GetFileName(cacheKey, cacheDir) + FAILOVER_FILE_SUFFIX
}
func GetConfigFailOverEncryptedDataKeyFileName(cacheKey, cacheDir string) string {
return GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir) + FAILOVER_FILE_SUFFIX
}
func WriteServicesToFile(service *model.Service, cacheKey, cacheDir string) {
err := file.MkdirIfNecessary(cacheDir)
if err != nil {
log.Printf("[ERROR]:faild to write name cache:%s ,value:%s ,err:%s \n", domFileName, string(sb), err.Error())
logger.Errorf("mkdir cacheDir failed,cacheDir:%s,err:", cacheDir, err)
return
}
bytes, _ := json.Marshal(service)
domFileName := GetFileName(cacheKey, cacheDir)
err = os.WriteFile(domFileName, bytes, 0666)
if err != nil {
logger.Errorf("failed to write name cache:%s ,value:%s ,err:%v", domFileName, string(bytes), err)
}
}
func ReadServicesFromFile(cacheDir string) map[string]model.Service {
files, err := ioutil.ReadDir(cacheDir)
files, err := os.ReadDir(cacheDir)
if err != nil {
log.Printf("[ERROR]:read cacheDir:%s failed!err:%s \n", cacheDir, err.Error())
logger.Errorf("read cacheDir:%s failed!err:%+v", cacheDir, err)
return nil
}
serviceMap := map[string]model.Service{}
for _, f := range files {
fileName := GetFileName(f.Name(), cacheDir)
b, err := ioutil.ReadFile(fileName)
b, err := os.ReadFile(fileName)
if err != nil {
log.Printf("[ERROR]:failed to read name cache file:%s,err:%s! ", fileName, err.Error())
logger.Errorf("failed to read name cache file:%s,err:%v ", fileName, err)
continue
}
s := string(b)
service := utils.JsonToService(s)
service := util.JsonToService(s)
if service == nil {
continue
}
serviceMap[f.Name()] = *service
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
serviceMap[cacheKey] = *service
}
log.Printf("finish loading name cache, total: " + strconv.Itoa(len(files)))
logger.Infof("finish loading name cache, total: %s", strconv.Itoa(len(files)))
return serviceMap
}
func WriteConfigToFile(cacheKey string, cacheDir string, content string) {
util.MkdirIfNecessary(cacheDir)
fileName := GetFileName(cacheKey, cacheDir)
err := ioutil.WriteFile(fileName, []byte(content), 0666)
func WriteConfigToFile(cacheKey string, cacheDir string, content string) error {
err := file.MkdirIfNecessary(cacheDir)
if err != nil {
log.Printf("[ERROR]:faild to write config cache:%s ,value:%s ,err:%s \n", fileName, string(content), err.Error())
errMsg := fmt.Sprintf("make dir failed, dir path %s, err: %v.", cacheDir, err)
logger.Error(errMsg)
return errors.New(errMsg)
}
err = writeConfigToFile(GetFileName(cacheKey, cacheDir), content, ConfigContent)
if err != nil {
logger.Error(err)
return err
}
return nil
}
func WriteEncryptedDataKeyToFile(cacheKey string, cacheDir string, content string) error {
err := file.MkdirIfNecessary(GetEncryptedDataKeyDir(cacheDir))
if err != nil {
errMsg := fmt.Sprintf("make dir failed, dir path %s, err: %v.", cacheDir, err)
logger.Error(errMsg)
return errors.New(errMsg)
}
err = writeConfigToFile(GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir), content, ConfigEncryptedDataKey)
if err != nil {
logger.Error(err)
return err
}
return nil
}
func writeConfigToFile(fileName string, content string, fileType ConfigCachedFileType) error {
if len(strings.TrimSpace(content)) == 0 {
// delete config snapshot
if err := os.Remove(fileName); err != nil {
if err != syscall.ENOENT {
logger.Debug(fmt.Sprintf("no need to delete %s cache file, file path %s, file doesn't exist.", fileType, fileName))
return nil
}
errMsg := fmt.Sprintf("failed to delete %s cache file, file path %s, err:%v", fileType, fileName, err)
return errors.New(errMsg)
}
}
err := os.WriteFile(fileName, []byte(content), 0666)
if err != nil {
errMsg := fmt.Sprintf("failed to write %s cache file, file name: %s, value: %s, err:%v", fileType, fileName, content, err)
return errors.New(errMsg)
}
return nil
}
func ReadEncryptedDataKeyFromFile(cacheKey string, cacheDir string) (string, error) {
content, err := readConfigFromFile(GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir), ConfigEncryptedDataKey)
if err != nil {
if errors.Is(err, fileNotExistError) {
logger.Warn(err)
return "", nil
}
}
return content, nil
}
func ReadConfigFromFile(cacheKey string, cacheDir string) (string, error) {
fileName := GetFileName(cacheKey, cacheDir)
b, err := ioutil.ReadFile(fileName)
return readConfigFromFile(GetFileName(cacheKey, cacheDir), ConfigEncryptedDataKey)
}
func readConfigFromFile(fileName string, fileType ConfigCachedFileType) (string, error) {
if !file.IsExistFile(fileName) {
errMsg := fmt.Sprintf("read cache file %s failed. cause file doesn't exist, file path: %s.", fileType, fileName)
return "", errors.Wrap(fileNotExistError, errMsg)
}
b, err := os.ReadFile(fileName)
if err != nil {
return "", errors.New(fmt.Sprintf("failed to read config cache file:%s,err:%s! ", fileName, err.Error()))
errMsg := fmt.Sprintf("get %s from cache failed, filePath:%s, error:%v ", fileType, fileName, err)
return "", errors.New(errMsg)
}
return string(b), nil
}
// GetFailover , get failover content
func GetFailover(key, dir string) string {
filePath := GetConfigFailOverContentFileName(key, dir)
return getFailOverConfig(filePath, ConfigContent)
}
func GetFailoverEncryptedDataKey(key, dir string) string {
filePath := GetConfigFailOverEncryptedDataKeyFileName(key, dir)
return getFailOverConfig(filePath, ConfigEncryptedDataKey)
}
func getFailOverConfig(filePath string, fileType ConfigCachedFileType) string {
if !file.IsExistFile(filePath) {
errMsg := fmt.Sprintf("read %s failed. cause file doesn't exist, file path: %s.", fileType, filePath)
logger.Warn(errMsg)
return ""
}
logger.Warnf("reading failover %s from path:%s", fileType, filePath)
fileContent, err := os.ReadFile(filePath)
if err != nil {
logger.Errorf("fail to read failover %s from %s", fileType, filePath)
return ""
}
return string(fileContent)
}

127
clients/cache/disk_cache_test.go vendored Normal file
View File

@ -0,0 +1,127 @@
package cache
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"math/rand"
"os"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
)
var (
dir = file.GetCurrentPath()
group = "FILE_GROUP"
ns = "chasu"
)
func TestWriteAndGetConfigToFile(t *testing.T) {
dataIdSuffix := strconv.Itoa(rand.Intn(1000))
t.Run("write and get config content", func(t *testing.T) {
dataId := "config_content" + dataIdSuffix
cacheKey := util.GetConfigCacheKey(dataId, group, ns)
configContent := "config content"
err := WriteConfigToFile(cacheKey, dir, "")
assert.Nil(t, err)
configFromFile, err := ReadConfigFromFile(cacheKey, dir)
assert.NotNil(t, err)
assert.Equal(t, configFromFile, "")
err = WriteConfigToFile(cacheKey, dir, configContent)
assert.Nil(t, err)
fromFile, err := ReadConfigFromFile(cacheKey, dir)
assert.Nil(t, err)
assert.Equal(t, fromFile, configContent)
err = WriteConfigToFile(cacheKey, dir, "")
assert.Nil(t, err)
configFromFile, err = ReadConfigFromFile(cacheKey, dir)
assert.Nil(t, err)
assert.Equal(t, configFromFile, "")
})
t.Run("write and get config encryptedDataKey", func(t *testing.T) {
dataId := "config_encryptedDataKey" + dataIdSuffix
cacheKey := util.GetConfigCacheKey(dataId, group, ns)
configContent := "config encrypted data key"
err := WriteEncryptedDataKeyToFile(cacheKey, dir, "")
assert.Nil(t, err)
configFromFile, err := ReadEncryptedDataKeyFromFile(cacheKey, dir)
assert.Nil(t, err)
assert.Equal(t, configFromFile, "")
err = WriteEncryptedDataKeyToFile(cacheKey, dir, configContent)
assert.Nil(t, err)
fromFile, err := ReadEncryptedDataKeyFromFile(cacheKey, dir)
assert.Nil(t, err)
assert.Equal(t, fromFile, configContent)
err = WriteEncryptedDataKeyToFile(cacheKey, dir, "")
assert.Nil(t, err)
configFromFile, err = ReadEncryptedDataKeyFromFile(cacheKey, dir)
assert.Nil(t, err)
assert.Equal(t, configFromFile, "")
})
t.Run("double write config file", func(t *testing.T) {
dataId := "config_encryptedDataKey" + dataIdSuffix
cacheKey := util.GetConfigCacheKey(dataId, group, ns)
configContent := "config encrypted data key"
err := WriteConfigToFile(cacheKey, dir, configContent)
assert.Nil(t, err)
err = WriteConfigToFile(cacheKey, dir, configContent)
assert.Nil(t, err)
fromFile, err := ReadConfigFromFile(cacheKey, dir)
assert.Nil(t, err)
assert.Equal(t, fromFile, configContent)
})
t.Run("read doesn't existed config file", func(t *testing.T) {
dataId := "config_encryptedDataKey" + dataIdSuffix + strconv.Itoa(rand.Intn(1000))
cacheKey := util.GetConfigCacheKey(dataId, group, ns)
_, err := ReadConfigFromFile(cacheKey, dir)
assert.NotNil(t, err)
_, err = ReadEncryptedDataKeyFromFile(cacheKey, dir)
assert.Nil(t, err)
})
}
func TestGetFailover(t *testing.T) {
cacheKey := "test_failOver"
fileContent := "test_failover"
t.Run("writeContent", func(t *testing.T) {
filepath := dir + string(os.PathSeparator) + cacheKey + "_failover"
fmt.Println(filepath)
err := writeFileContent(filepath, fileContent)
assert.Nil(t, err)
})
t.Run("getContent", func(t *testing.T) {
content := GetFailover(cacheKey, dir)
assert.Equal(t, content, fileContent)
})
t.Run("clearContent", func(t *testing.T) {
filepath := dir + string(os.PathSeparator) + cacheKey + "_failover"
err := writeFileContent(filepath, "")
assert.Nil(t, err)
})
}
// write file content
func writeFileContent(filepath, content string) error {
return os.WriteFile(filepath, []byte(content), 0666)
}

View File

@ -1,83 +1,128 @@
/*
* Copyright 1999-2020 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.
*/
package clients
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// 创建配置相关的客户端
func CreateConfigClient(properties map[string]interface{}) (iClient config_client.IConfigClient,
err error) {
nacosClient, errSetConfig := setConfig(properties)
if errSetConfig != nil {
err = errSetConfig
return
}
nacosClient.SetHttpAgent(&http_agent.HttpAgent{})
config, errNew := config_client.NewConfigClient(nacosClient)
if errNew != nil {
err = errNew
return
}
iClient = &config
return
// CreateConfigClient use to create config client
func CreateConfigClient(properties map[string]interface{}) (iClient config_client.IConfigClient, err error) {
param := getConfigParam(properties)
return NewConfigClient(param)
}
// 创建服务发现相关的客户端
// CreateNamingClient use to create a nacos naming client
func CreateNamingClient(properties map[string]interface{}) (iClient naming_client.INamingClient, err error) {
nacosClient, errSetConfig := setConfig(properties)
if errSetConfig != nil {
err = errSetConfig
param := getConfigParam(properties)
return NewNamingClient(param)
}
func NewConfigClient(param vo.NacosClientParam) (iClient config_client.IConfigClient, err error) {
nacosClient, err := setConfig(param)
if err != nil {
return
}
nacosClient.SetHttpAgent(&http_agent.HttpAgent{})
naming, errNew := naming_client.NewNamingClient(nacosClient)
if errNew != nil {
err = errNew
config, err := config_client.NewConfigClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}
iClient = &naming
iClient = config
return
}
func setConfig(properties map[string]interface{}) (iClient nacos_client.INacosClient, err error) {
client := nacos_client.NacosClient{}
func NewNamingClient(param vo.NacosClientParam) (iClient naming_client.INamingClient, err error) {
nacosClient, err := setConfig(param)
if err != nil {
return
}
naming, err := naming_client.NewNamingClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}
iClient = naming
return
}
func getConfigParam(properties map[string]interface{}) (param vo.NacosClientParam) {
if clientConfigTmp, exist := properties[constant.KEY_CLIENT_CONFIG]; exist {
if clientConfig, ok := clientConfigTmp.(constant.ClientConfig); ok {
err = client.SetClientConfig(clientConfig)
if err != nil {
return
}
param.ClientConfig = &clientConfig
}
} else {
_ = client.SetClientConfig(constant.ClientConfig{
TimeoutMs: 30 * 1000,
ListenInterval: 10 * 1000,
BeatInterval: 5 * 1000,
})
}
// 设置 serverConfig
if serverConfigTmp, exist := properties[constant.KEY_SERVER_CONFIGS]; exist {
if serverConfigs, ok := serverConfigTmp.([]constant.ServerConfig); ok {
err = client.SetServerConfig(serverConfigs)
if err != nil {
return
}
param.ServerConfigs = serverConfigs
}
}
return
}
func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, err error) {
client := &nacos_client.NacosClient{}
if param.ClientConfig == nil {
// default clientConfig
_ = client.SetClientConfig(constant.ClientConfig{
TimeoutMs: 10 * 1000,
BeatInterval: 5 * 1000,
})
} else {
err = client.SetClientConfig(*param.ClientConfig)
if err != nil {
return nil, err
}
}
if len(param.ServerConfigs) == 0 {
clientConfig, _ := client.GetClientConfig()
if len(clientConfig.Endpoint) <= 0 {
err = errors.New("server configs not found in properties")
return
return nil, err
}
_ = client.SetServerConfig(nil)
} else {
for i := range param.ServerConfigs {
if param.ServerConfigs[i].Port == 0 {
param.ServerConfigs[i].Port = 8848
}
if param.ServerConfigs[i].GrpcPort == 0 {
param.ServerConfigs[i].GrpcPort = param.ServerConfigs[i].Port + constant.RpcPortOffset
}
}
err = client.SetServerConfig(param.ServerConfigs)
if err != nil {
return nil, err
}
client.SetServerConfig([]constant.ServerConfig{})
}
iClient = &client
if _, _err := client.GetHttpAgent(); _err != nil {
if clientCfg, err := client.GetClientConfig(); err == nil {
_ = client.SetHttpAgent(&http_agent.HttpAgent{TlsConfig: clientCfg.TLSCfg})
}
}
iClient = client
return
}

View File

@ -0,0 +1,68 @@
package clients
import (
"net"
"reflect"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
func getIntranetIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "127.0.0.1"
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return "127.0.0.1"
}
func TestSetConfigClient(t *testing.T) {
ip := getIntranetIP()
sc := []constant.ServerConfig{
*constant.NewServerConfig(
ip,
8848,
),
}
cc := *constant.NewClientConfig(
constant.WithNamespaceId("public"),
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
constant.WithCacheDir("/tmp/nacos/cache"),
constant.WithLogLevel("debug"),
)
t.Run("setConfig_error", func(t *testing.T) {
nacosClient, err := setConfig(vo.NacosClientParam{})
assert.Nil(t, nacosClient)
assert.Equal(t, "server configs not found in properties", err.Error())
})
t.Run("setConfig_normal", func(t *testing.T) {
// use map params setConfig
param := getConfigParam(map[string]interface{}{
"serverConfigs": sc,
"clientConfig": cc,
})
nacosClientFromMap, err := setConfig(param)
assert.Nil(t, err)
nacosClientFromStruct, err := setConfig(vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sc,
})
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(nacosClientFromMap, nacosClientFromStruct))
})
}

View File

@ -1,383 +1,563 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/common/util"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
"io/ioutil"
"log"
"net/http"
"context"
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
nacos_inner_encryption "github.com/nacos-group/nacos-sdk-go/v2/common/encryption"
"github.com/nacos-group/nacos-sdk-go/v2/common/filter"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/pkg/errors"
)
const (
perTaskConfigSize = 3000
executorErrDelay = 5 * time.Second
)
type ConfigClient struct {
ctx context.Context
cancel context.CancelFunc
nacos_client.INacosClient
kmsClient *kms.Client
localConfigs []vo.ConfigParam
mutex sync.Mutex
configProxy ConfigProxy
configCacheDir string
configFilterChainManager filter.IConfigFilterChain
localConfigs []vo.ConfigParam
mutex sync.Mutex
configProxy IConfigProxy
configCacheDir string
lastAllSyncTime time.Time
cacheMap cache.ConcurrentMap
uid string
listenExecute chan struct{}
isClosed bool
}
func NewConfigClient(nc nacos_client.INacosClient) (ConfigClient, error) {
config := ConfigClient{}
type cacheData struct {
isInitializing bool
dataId string
group string
content string
contentType string
encryptedDataKey string
tenant string
cacheDataListener *cacheDataListener
md5 string
appName string
taskId int
configClient *ConfigClient
isSyncWithServer bool
}
type cacheDataListener struct {
listener vo.Listener
lastMd5 string
}
func (cacheData *cacheData) executeListener() {
cacheData.cacheDataListener.lastMd5 = cacheData.md5
cacheData.configClient.cacheMap.Set(util.GetConfigCacheKey(cacheData.dataId, cacheData.group, cacheData.tenant), *cacheData)
param := &vo.ConfigParam{
DataId: cacheData.dataId,
Content: cacheData.content,
EncryptedDataKey: cacheData.encryptedDataKey,
UsageType: vo.ResponseType,
}
if err := cacheData.configClient.configFilterChainManager.DoFilters(param); err != nil {
logger.Errorf("do filters failed ,dataId=%s,group=%s,tenant=%s,err:%+v ", cacheData.dataId,
cacheData.group, cacheData.tenant, err)
return
}
decryptedContent := param.Content
go cacheData.cacheDataListener.listener(cacheData.tenant, cacheData.group, cacheData.dataId, decryptedContent)
}
func NewConfigClientWithRamCredentialProvider(nc nacos_client.INacosClient, provider security.RamCredentialProvider) (*ConfigClient, error) {
config := &ConfigClient{}
config.ctx, config.cancel = context.WithCancel(context.Background())
config.INacosClient = nc
clientConfig, err := nc.GetClientConfig()
if err != nil {
return config, err
return nil, err
}
serverConfig, err := nc.GetServerConfig()
if err != nil {
return config, err
return nil, err
}
httpAgent, err := nc.GetHttpAgent()
if err != nil {
return config, err
}
err = logger.InitLog(clientConfig.LogDir)
if err != nil {
return config, err
}
config.configCacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config"
config.configProxy, err = NewConfigProxy(serverConfig, clientConfig, httpAgent)
if clientConfig.OpenKMS {
kmsClient, err := kms.NewClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
if err != nil {
return config, err
}
config.kmsClient = kmsClient
return nil, err
}
if err = initLogger(clientConfig); err != nil {
return nil, err
}
clientConfig.CacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config"
config.configCacheDir = clientConfig.CacheDir
if config.configProxy, err = NewConfigProxyWithRamCredentialProvider(config.ctx, serverConfig, clientConfig, httpAgent, provider); err != nil {
return nil, err
}
config.configFilterChainManager = filter.NewConfigFilterChainManager()
if clientConfig.OpenKMS {
kmsEncryptionHandler := nacos_inner_encryption.NewKmsHandler()
nacos_inner_encryption.RegisterConfigEncryptionKmsPlugins(kmsEncryptionHandler, clientConfig)
encryptionFilter := filter.NewDefaultConfigEncryptionFilter(kmsEncryptionHandler)
err := filter.RegisterConfigFilterToChain(config.configFilterChainManager, encryptionFilter)
if err != nil {
logger.Error(err)
}
}
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
config.uid = uid.String()
config.cacheMap = cache.NewConcurrentMap()
config.listenExecute = make(chan struct{})
config.startInternal()
return config, err
}
func (client *ConfigClient) sync() (clientConfig constant.ClientConfig,
serverConfigs []constant.ServerConfig, agent http_agent.IHttpAgent, err error) {
clientConfig, err = client.GetClientConfig()
if err != nil {
log.Println(err, ";do you call client.SetClientConfig()?")
}
if err == nil {
serverConfigs, err = client.GetServerConfig()
if err != nil {
log.Println(err, ";do you call client.SetServerConfig()?")
}
}
if err == nil {
agent, err = client.GetHttpAgent()
if err != nil {
log.Println(err, ";do you call client.SetHttpAgent()?")
}
}
return
func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
return NewConfigClientWithRamCredentialProvider(nc, nil)
}
func initLogger(clientConfig constant.ClientConfig) error {
return logger.InitLogger(logger.BuildLoggerConfig(clientConfig))
}
func (client *ConfigClient) GetConfig(param vo.ConfigParam) (content string, err error) {
content, err = client.getConfigInner(param)
content, encryptedDataKey, err := client.getConfigInner(param)
if err != nil {
return "", err
}
return client.decrypt(param.DataId, content)
}
func (client *ConfigClient) decrypt(dataId, content string) (string, error) {
if strings.HasPrefix(dataId, "cipher-") && client.kmsClient != nil {
request := kms.CreateDecryptRequest()
request.Method = "POST"
request.Scheme = "https"
request.AcceptFormat = "json"
request.CiphertextBlob = content
response, err := client.kmsClient.Decrypt(request)
if err != nil {
return "", errors.New("ksm decrypt failed")
}
content = response.Plaintext
deepCopyParam := param.DeepCopy()
deepCopyParam.EncryptedDataKey = encryptedDataKey
deepCopyParam.Content = content
deepCopyParam.UsageType = vo.ResponseType
if err = client.configFilterChainManager.DoFilters(deepCopyParam); err != nil {
return "", err
}
content = deepCopyParam.Content
return content, nil
}
func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content string, err error) {
func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content, encryptedDataKey string, err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.GetConfig] param.dataId can not be empty")
return "", "", err
}
if len(param.Group) <= 0 {
err = errors.New("[client.GetConfig] param.group can not be empty")
param.Group = constant.DEFAULT_GROUP
}
clientConfig, _ := client.GetClientConfig()
cacheKey := utils.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
content, err = client.configProxy.GetConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err != nil {
log.Printf("[ERROR] get config from server error:%s ", err.Error())
if _, ok := err.(*nacos_error.NacosError); ok {
nacosErr := err.(*nacos_error.NacosError)
if nacosErr.ErrorCode() == "404" {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, "")
return "", errors.New("config not found")
}
if nacosErr.ErrorCode() == "403" {
return "", errors.New("get config forbidden")
}
}
content, err = cache.ReadConfigFromFile(cacheKey, client.configCacheDir)
if err != nil {
log.Printf("[ERROR] get config from cache error:%s ", err.Error())
return "", errors.New("read config from both server and cache fail")
}
} else {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, content)
cacheKey := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
content = cache.GetFailover(cacheKey, client.configCacheDir)
if len(content) > 0 {
logger.Warnf("%s %s %s is using failover content!", clientConfig.NamespaceId, param.Group, param.DataId)
encryptedDataKey = cache.GetFailoverEncryptedDataKey(cacheKey, client.configCacheDir)
return content, encryptedDataKey, nil
}
return content, nil
response, err := client.configProxy.queryConfig(param.DataId, param.Group, clientConfig.NamespaceId,
clientConfig.TimeoutMs, false, client)
if err != nil {
logger.Errorf("get config from server error:%v, dataId=%s, group=%s, namespaceId=%s", err,
param.DataId, param.Group, clientConfig.NamespaceId)
if clientConfig.DisableUseSnapShot {
return "", "", errors.Errorf("get config from remote nacos server fail, and is not allowed to read local file, err:%v", err)
}
cacheContent, cacheErr := cache.ReadConfigFromFile(cacheKey, client.configCacheDir)
if cacheErr != nil {
return "", "", errors.Errorf("read config from both server and cache fail, err=%vdataId=%s, group=%s, namespaceId=%s",
cacheErr, param.DataId, param.Group, clientConfig.NamespaceId)
}
if !strings.HasPrefix(param.DataId, nacos_inner_encryption.CipherPrefix) {
return cacheContent, "", nil
}
encryptedDataKey, cacheErr = cache.ReadEncryptedDataKeyFromFile(cacheKey, client.configCacheDir)
if cacheErr != nil {
return "", "", errors.Errorf("read encryptedDataKey from server and cache fail, err=%vdataId=%s, group=%s, namespaceId=%s",
cacheErr, param.DataId, param.Group, clientConfig.NamespaceId)
}
logger.Warnf("read config from cache success, dataId=%s, group=%s, namespaceId=%s", param.DataId, param.Group, clientConfig.NamespaceId)
return cacheContent, encryptedDataKey, nil
}
if response != nil && response.Response != nil && !response.IsSuccess() {
return response.Content, response.EncryptedDataKey, errors.New(response.GetMessage())
}
encryptedDataKey = response.EncryptedDataKey
content = response.Content
return content, encryptedDataKey, nil
}
func (client *ConfigClient) PublishConfig(param vo.ConfigParam) (published bool,
err error) {
func (client *ConfigClient) PublishConfig(param vo.ConfigParam) (published bool, err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.PublishConfig] param.dataId can not be empty")
}
if len(param.Group) <= 0 {
err = errors.New("[client.PublishConfig] param.group can not be empty")
return
}
if len(param.Content) <= 0 {
err = errors.New("[client.PublishConfig] param.content can not be empty")
return
}
if len(param.Group) <= 0 {
param.Group = constant.DEFAULT_GROUP
}
param.UsageType = vo.RequestType
if err = client.configFilterChainManager.DoFilters(&param); err != nil {
return false, err
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.PublishConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
request := rpc_request.NewConfigPublishRequest(param.Group, param.DataId, clientConfig.NamespaceId, param.Content, param.CasMd5)
request.AdditionMap["tag"] = param.Tag
request.AdditionMap["config_tags"] = param.ConfigTags
request.AdditionMap["appName"] = param.AppName
request.AdditionMap["betaIps"] = param.BetaIps
request.AdditionMap["type"] = param.Type
request.AdditionMap["src_user"] = param.SrcUser
request.AdditionMap["encryptedDataKey"] = param.EncryptedDataKey
rpcClient := client.configProxy.getRpcClient(client)
response, err := client.configProxy.requestProxy(rpcClient, request, constant.DEFAULT_TIMEOUT_MILLS)
if err != nil {
return false, err
}
if response != nil {
return client.buildResponse(response)
}
return false, err
}
func (client *ConfigClient) DeleteConfig(param vo.ConfigParam) (deleted bool,
err error) {
func (client *ConfigClient) DeleteConfig(param vo.ConfigParam) (deleted bool, err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.DeleteConfig] param.dataId can not be empty")
}
if len(param.Group) <= 0 {
err = errors.New("[client.DeleteConfig] param.group can not be empty")
param.Group = constant.DEFAULT_GROUP
}
if err != nil {
return false, err
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.DeleteConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
request := rpc_request.NewConfigRemoveRequest(param.Group, param.DataId, clientConfig.NamespaceId)
rpcClient := client.configProxy.getRpcClient(client)
response, err := client.configProxy.requestProxy(rpcClient, request, constant.DEFAULT_TIMEOUT_MILLS)
if err != nil {
return false, err
}
if response != nil {
return client.buildResponse(response)
}
return false, err
}
func (client *ConfigClient) AddConfigToListen(params []vo.ConfigParam) (err error) {
client.mutex.Lock()
defer client.mutex.Unlock()
var newParams []vo.ConfigParam
// 去掉重复添加的
for _, newParam := range params {
flag := true
for _, param := range client.localConfigs {
if param.Group == newParam.Group && param.DataId == newParam.DataId {
flag = false
break
}
}
if flag {
newParams = append(newParams, newParam)
}
// Cancel Listen Config
func (client *ConfigClient) CancelListenConfig(param vo.ConfigParam) (err error) {
clientConfig, err := client.GetClientConfig()
if err != nil {
logger.Errorf("[checkConfigInfo.GetClientConfig] failed,err:%+v", err)
return
}
client.localConfigs = append(client.localConfigs, newParams...)
return
client.cacheMap.Remove(util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId))
logger.Infof("Cancel listen config DataId:%s Group:%s", param.DataId, param.Group)
return err
}
func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
go func() {
for {
clientConfig, serverConfigs, agent, err := client.sync()
// 创建计时器
var timer *time.Timer
if err == nil {
timer = time.NewTimer(time.Duration(clientConfig.ListenInterval) * time.Millisecond)
}
client.listenConfigTask(clientConfig, serverConfigs, agent, param)
<-timer.C
}
}()
return nil
}
func (client *ConfigClient) listenConfigTask(clientConfig constant.ClientConfig,
serverConfigs []constant.ServerConfig, agent http_agent.IHttpAgent, param vo.ConfigParam) {
var listeningConfigs string
// 检查&拼接监听参数
client.mutex.Lock()
if len(param.DataId) <= 0 {
log.Fatalf("[client.ListenConfig] DataId can not be empty")
return
err = errors.New("[client.ListenConfig] DataId can not be empty")
return err
}
if len(param.Group) <= 0 {
log.Fatalf("[client.ListenConfig] Group can not be empty")
return
err = errors.New("[client.ListenConfig] Group can not be empty")
return err
}
var tenant string
if len(clientConfig.NamespaceId) > 0 {
tenant = clientConfig.NamespaceId
clientConfig, err := client.GetClientConfig()
if err != nil {
err = errors.New("[checkConfigInfo.GetClientConfig] failed")
return err
}
for _, config := range client.localConfigs {
if config.Group == param.Group && config.DataId == param.DataId {
param.Content = config.Content
break
}
}
var md5 string
if len(param.Content) > 0 {
md5 = util.Md5(param.Content)
}
if len(tenant) > 0 {
listeningConfigs += param.DataId + constant.SPLIT_CONFIG_INNER + param.Group + constant.SPLIT_CONFIG_INNER +
md5 + constant.SPLIT_CONFIG_INNER + tenant + constant.SPLIT_CONFIG
key := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
var cData cacheData
if v, ok := client.cacheMap.Get(key); ok {
cData = v.(cacheData)
cData.isInitializing = true
} else {
listeningConfigs += param.DataId + constant.SPLIT_CONFIG_INNER + param.Group + constant.SPLIT_CONFIG_INNER +
md5 + constant.SPLIT_CONFIG
}
var (
content string
md5Str string
innerErr error
)
if content, innerErr = cache.ReadConfigFromFile(key, client.configCacheDir); innerErr != nil {
logger.Warn(innerErr)
}
encryptedDataKey, _ := cache.ReadEncryptedDataKeyFromFile(key, client.configCacheDir)
if len(content) > 0 {
md5Str = util.Md5(content)
}
listener := &cacheDataListener{
listener: param.OnChange,
lastMd5: md5Str,
}
client.mutex.Unlock()
// http 请求
params := make(map[string]string)
params[constant.KEY_LISTEN_CONFIGS] = listeningConfigs
var changed string
for _, serverConfig := range client.configProxy.GetServerList() {
path := client.buildBasePath(serverConfig) + "/listener"
changedTmp, err := listen(agent, path, clientConfig.TimeoutMs, clientConfig.ListenInterval, params)
if err == nil {
changed = changedTmp
break
} else {
if _, ok := err.(*nacos_error.NacosError); ok {
changed = changedTmp
break
} else {
log.Println("[client.ListenConfig] listen config error:", err.Error())
}
}
}
if strings.ToLower(strings.Trim(changed, " ")) == "" {
log.Println("[client.ListenConfig] no change")
} else {
log.Print("[client.ListenConfig] config changed:" + changed)
client.updateLocalConfig(changed, param)
}
}
func listen(agent http_agent.IHttpAgent, path string,
timeoutMs uint64, listenInterval uint64,
params map[string]string) (changed string, err error) {
header := map[string][]string{
"Content-Type": {"application/x-www-form-urlencoded"},
"Long-Pulling-Timeout": {strconv.FormatUint(listenInterval, 10)},
}
log.Println("[client.ListenConfig] request url:", path, " ;params:", params, " ;header:", header)
var response *http.Response
response, err = agent.Post(path, header, timeoutMs, params)
if err == nil {
bytes, errRead := ioutil.ReadAll(response.Body)
defer response.Body.Close()
if errRead != nil {
err = errRead
} else {
if response.StatusCode == 200 {
changed = string(bytes)
} else {
err = nacos_error.NewNacosError(strconv.Itoa(response.StatusCode), string(bytes), nil)
}
cData = cacheData{
isInitializing: true,
dataId: param.DataId,
group: param.Group,
tenant: clientConfig.NamespaceId,
content: content,
md5: md5Str,
cacheDataListener: listener,
encryptedDataKey: encryptedDataKey,
taskId: client.cacheMap.Count() / perTaskConfigSize,
configClient: client,
}
}
client.cacheMap.Set(key, cData)
return
}
func (client *ConfigClient) updateLocalConfig(changed string, param vo.ConfigParam) {
func (client *ConfigClient) SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error) {
return client.searchConfigInner(param)
}
func (client *ConfigClient) CloseClient() {
client.mutex.Lock()
defer client.mutex.Unlock()
changedConfigs := strings.Split(changed, "%01")
for _, config := range changedConfigs {
attrs := strings.Split(config, "%02")
if len(attrs) == 2 {
content, err := client.getConfigInner(vo.ConfigParam{
DataId: attrs[0],
Group: attrs[1],
})
if err != nil {
log.Println("[client.updateLocalConfig] update config failed:", err.Error())
} else {
client.putLocalConfig(vo.ConfigParam{
DataId: attrs[0],
Group: attrs[1],
Content: content,
})
// call listener:
decrept, _ := client.decrypt(attrs[0], content)
param.OnChange("", attrs[1], attrs[0], decrept)
}
} else if len(attrs) == 3 {
content, err := client.getConfigInner(vo.ConfigParam{
DataId: attrs[0],
Group: attrs[1],
})
if err != nil {
log.Println("[client.updateLocalConfig] update config failed:", err.Error())
} else {
client.putLocalConfig(vo.ConfigParam{
DataId: attrs[0],
Group: attrs[1],
Content: content,
})
// call listener:
decrept, _ := client.decrypt(attrs[0], content)
param.OnChange(attrs[2], attrs[1], attrs[0], decrept)
}
}
if client.isClosed {
return
}
log.Println("[client.updateLocalConfig] update config complete")
log.Println("[client.localConfig] ", client.localConfigs)
client.configProxy.getRpcClient(client).Shutdown()
client.cancel()
client.isClosed = true
}
func (client *ConfigClient) putLocalConfig(config vo.ConfigParam) {
if len(config.DataId) > 0 && len(config.Group) > 0 {
exist := false
for i := 0; i < len(client.localConfigs); i++ {
if len(client.localConfigs[i].DataId) > 0 && len(client.localConfigs[i].Group) > 0 &&
config.DataId == client.localConfigs[i].DataId && config.Group == client.localConfigs[i].Group {
// 本地存在 则更新
client.localConfigs[i] = config
exist = true
break
func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*model.ConfigPage, error) {
if param.Search != "accurate" && param.Search != "blur" {
return nil, errors.New("[client.searchConfigInner] param.search must be accurate or blur")
}
if param.PageNo <= 0 {
param.PageNo = 1
}
if param.PageSize <= 0 {
param.PageSize = 10
}
clientConfig, _ := client.GetClientConfig()
configItems, err := client.configProxy.searchConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err != nil {
logger.Errorf("search config from server error:%+v ", err)
if _, ok := err.(*nacos_error.NacosError); ok {
nacosErr := err.(*nacos_error.NacosError)
if nacosErr.ErrorCode() == "404" {
return nil, errors.New("config not found")
}
if nacosErr.ErrorCode() == "403" {
return nil, errors.New("get config forbidden")
}
}
if !exist {
// 本地不存在 放入
client.localConfigs = append(client.localConfigs, config)
}
return nil, err
}
log.Println("[client.putLocalConfig] putLocalConfig success")
return configItems, nil
}
func (client *ConfigClient) buildBasePath(serverConfig constant.ServerConfig) (basePath string) {
basePath = "http://" + serverConfig.IpAddr + ":" +
strconv.FormatUint(serverConfig.Port, 10) + serverConfig.ContextPath + constant.CONFIG_PATH
return
func (client *ConfigClient) startInternal() {
go func() {
timer := time.NewTimer(executorErrDelay)
defer timer.Stop()
for {
select {
case <-client.listenExecute:
client.executeConfigListen()
case <-timer.C:
client.executeConfigListen()
case <-client.ctx.Done():
return
}
timer.Reset(executorErrDelay)
}
}()
}
func (client *ConfigClient) executeConfigListen() {
var (
needAllSync = time.Since(client.lastAllSyncTime) >= constant.ALL_SYNC_INTERNAL
hasChangedKeys = false
)
listenTaskMap := client.buildListenTask(needAllSync)
if len(listenTaskMap) == 0 {
return
}
for taskId, caches := range listenTaskMap {
request := buildConfigBatchListenRequest(caches)
rpcClient := client.configProxy.createRpcClient(client.ctx, fmt.Sprintf("%d", taskId), client)
iResponse, err := client.configProxy.requestProxy(rpcClient, request, 3000)
if err != nil {
logger.Warnf("ConfigBatchListenRequest failure, err:%v", err)
continue
}
if iResponse == nil {
logger.Warnf("ConfigBatchListenRequest failure, response is nil")
continue
}
if !iResponse.IsSuccess() {
logger.Warnf("ConfigBatchListenRequest failure, error code:%d", iResponse.GetErrorCode())
continue
}
response, ok := iResponse.(*rpc_response.ConfigChangeBatchListenResponse)
if !ok {
continue
}
if len(response.ChangedConfigs) > 0 {
hasChangedKeys = true
}
changeKeys := make(map[string]struct{}, len(response.ChangedConfigs))
for _, v := range response.ChangedConfigs {
changeKey := util.GetConfigCacheKey(v.DataId, v.Group, v.Tenant)
changeKeys[changeKey] = struct{}{}
if value, ok := client.cacheMap.Get(changeKey); ok {
cData := value.(cacheData)
client.refreshContentAndCheck(cData, !cData.isInitializing)
}
}
for _, v := range client.cacheMap.Items() {
data := v.(cacheData)
changeKey := util.GetConfigCacheKey(data.dataId, data.group, data.tenant)
if _, ok := changeKeys[changeKey]; !ok {
data.isSyncWithServer = true
client.cacheMap.Set(changeKey, data)
continue
}
data.isInitializing = true
client.cacheMap.Set(changeKey, data)
}
}
if needAllSync {
client.lastAllSyncTime = time.Now()
}
if hasChangedKeys {
client.asyncNotifyListenConfig()
}
monitor.GetListenConfigCountMonitor().Set(float64(client.cacheMap.Count()))
}
func buildConfigBatchListenRequest(caches []cacheData) *rpc_request.ConfigBatchListenRequest {
request := rpc_request.NewConfigBatchListenRequest(len(caches))
for _, cache := range caches {
request.ConfigListenContexts = append(request.ConfigListenContexts,
model.ConfigListenContext{Group: cache.group, Md5: cache.md5, DataId: cache.dataId, Tenant: cache.tenant})
}
return request
}
func (client *ConfigClient) refreshContentAndCheck(cacheData cacheData, notify bool) {
configQueryResponse, err := client.configProxy.queryConfig(cacheData.dataId, cacheData.group, cacheData.tenant,
constant.DEFAULT_TIMEOUT_MILLS, notify, client)
if err != nil {
logger.Errorf("refresh content and check md5 fail ,dataId=%s,group=%s,tenant=%s ", cacheData.dataId,
cacheData.group, cacheData.tenant)
return
}
if configQueryResponse != nil && configQueryResponse.Response != nil && !configQueryResponse.IsSuccess() {
logger.Errorf("refresh cached config from server error:%v, dataId=%s, group=%s", configQueryResponse.GetMessage(),
cacheData.dataId, cacheData.group)
return
}
cacheData.content = configQueryResponse.Content
cacheData.contentType = configQueryResponse.ContentType
cacheData.encryptedDataKey = configQueryResponse.EncryptedDataKey
if notify {
logger.Infof("[config_rpc_client] [data-received] dataId=%s, group=%s, tenant=%s, md5=%s, content=%s, type=%s",
cacheData.dataId, cacheData.group, cacheData.tenant, cacheData.md5,
util.TruncateContent(cacheData.content), cacheData.contentType)
}
cacheData.md5 = util.Md5(cacheData.content)
if cacheData.md5 != cacheData.cacheDataListener.lastMd5 {
cacheDataPtr := &cacheData
cacheDataPtr.executeListener()
}
}
func (client *ConfigClient) buildListenTask(needAllSync bool) map[int][]cacheData {
listenTaskMap := make(map[int][]cacheData, 8)
for _, v := range client.cacheMap.Items() {
data, ok := v.(cacheData)
if !ok {
continue
}
if data.isSyncWithServer {
if data.md5 != data.cacheDataListener.lastMd5 {
data.executeListener()
}
if !needAllSync {
continue
}
}
listenTaskMap[data.taskId] = append(listenTaskMap[data.taskId], data)
}
return listenTaskMap
}
func (client *ConfigClient) asyncNotifyListenConfig() {
go func() {
client.listenExecute <- struct{}{}
}()
}
func (client *ConfigClient) buildResponse(response rpc_response.IResponse) (bool, error) {
if response.IsSuccess() {
return response.IsSuccess(), nil
}
return false, errors.New(response.GetMessage())
}

View File

@ -1,43 +1,70 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-08 10:03
**/
//go:generate mockgen -destination ../../mock/mock_config_client_interface.go -package mock -source=./config_client_interface.go
type IConfigClient interface {
// 获取配置
// GetConfig use to get config from nacos server
// dataId require
// group require
// tenant ==>nacos.namespace optional
GetConfig(param vo.ConfigParam) (string, error)
// 发布配置
// PublishConfig use to publish config to nacos server
// dataId require
// group require
// content require
// tenant ==>nacos.namespace optional
PublishConfig(param vo.ConfigParam) (bool, error)
// 删除配置
// DeleteConfig use to delete config
// dataId require
// group require
// tenant ==>nacos.namespace optional
DeleteConfig(param vo.ConfigParam) (bool, error)
// 监听配置
// ListenConfig use to listen config change,it will callback OnChange() when config change
// dataId require
// group require
// onchange require
// tenant ==>nacos.namespace optional
ListenConfig(params vo.ConfigParam) (err error)
//CancelListenConfig use to cancel listen config change
// dataId require
// group require
// tenant ==>nacos.namespace optional
ListenConfig(params vo.ConfigParam) (err error)
CancelListenConfig(params vo.ConfigParam) (err error)
// SearchConfig use to search nacos config
// search require search=accurate--精确搜索 search=blur--模糊搜索
// group option
// dataId option
// tenant ==>nacos.namespace optional
// pageNo option,default is 1
// pageSize option,default is 10
SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error)
// CloseClient Close the GRPC client
CloseClient()
}

View File

@ -1,49 +1,64 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"fmt"
"github.com/golang/mock/gomock"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/mock"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/stretchr/testify/assert"
"net/http"
"context"
"errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-16 21:01
**/
var serverConfigWithOptions = constant.NewServerConfig("127.0.0.1", 8848)
var clientConfigTest = constant.ClientConfig{
TimeoutMs: 20000,
ListenInterval: 10000,
BeatInterval: 10000,
}
var clientConfigWithOptions = constant.NewClientConfig(
constant.WithTimeoutMs(10*1000),
constant.WithBeatInterval(2*1000),
constant.WithNotLoadCacheAtStart(true),
constant.WithAccessKey("LTAxxx"),
constant.WithSecretKey("EdPxxx"),
constant.WithOpenKMS(true),
constant.WithKMSVersion(constant.KMSv1),
constant.WithRegionId("cn-hangzhou"),
)
var serverConfigTest = constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "console.nacos.io",
}
var clientTLsConfigWithOptions = constant.NewClientConfig(
constant.WithTimeoutMs(10*1000),
constant.WithBeatInterval(2*1000),
constant.WithNotLoadCacheAtStart(true),
var configParamMapTest = map[string]string{
"dataId": "dataId",
"group": "group",
}
var configParamTest = vo.ConfigParam{
DataId: "dataId",
Group: "group",
}
/*constant.WithTLS(constant.TLSConfig{
Enable: true,
TrustAll: false,
CaFile: "mse-nacos-ca.cer",
}),*/
)
var localConfigTest = vo.ConfigParam{
DataId: "dataId",
@ -51,171 +66,183 @@ var localConfigTest = vo.ConfigParam{
Content: "content",
}
var localConfigMapTest = map[string]string{
"dataId": "dataId",
"group": "group",
"content": "content",
}
var headerTest = map[string][]string{
"Content-Type": {"application/x-www-form-urlencoded"},
}
var serverConfigsTest = []constant.ServerConfig{serverConfigTest}
var httpAgentTest = mock.MockIHttpAgent{}
func cretateConfigClientTest() ConfigClient {
func createConfigClientTest() *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(&http_agent.HttpAgent{})
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
func cretateConfigClientHttpTest(mockHttpAgent http_agent.IHttpAgent) ConfigClient {
func createConfigClientTestTls() *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockHttpAgent)
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientTLsConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
// sync
func Test_SyncWithoutClientConfig(t *testing.T) {
client := cretateConfigClientTest()
_, _, _, err := client.sync()
assert.NotNil(t, err)
func createConfigClientCommon() *ConfigClient {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
func Test_SyncWithoutServerConfig(t *testing.T) {
client := cretateConfigClientTest()
_ = client.SetClientConfig(clientConfigTest)
_, _, _, err := client.sync()
assert.NotNil(t, err)
func createConfigClientForKms() *ConfigClient {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxyForUsingLocalDiskCache{}
return client
}
func Test_SyncWithoutHttpAgent(t *testing.T) {
client := cretateConfigClientTest()
_ = client.SetServerConfig(serverConfigsTest)
_ = client.SetClientConfig(clientConfigTest)
_, _, _, err := client.sync()
assert.NotNil(t, err)
type MockConfigProxyForUsingLocalDiskCache struct {
MockConfigProxy
}
func Test_Sync(t *testing.T) {
client := cretateConfigClientTest()
_ = client.SetClientConfig(clientConfigTest)
_ = client.SetServerConfig(serverConfigsTest)
_ = client.SetHttpAgent(&httpAgentTest)
_, _, _, err := client.sync()
assert.Nil(t, err)
func (m *MockConfigProxyForUsingLocalDiskCache) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
return nil, errors.New("mock err for using localCache")
}
type MockConfigProxy struct {
}
func (m *MockConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
cacheKey := util.GetConfigCacheKey(dataId, group, tenant)
if IsLimited(cacheKey) {
return nil, errors.New("request is limited")
}
return &rpc_response.ConfigQueryResponse{Content: "hello world", Response: &rpc_response.Response{Success: true}}, nil
}
func (m *MockConfigProxy) searchConfigProxy(param vo.SearchConfigParam, tenant, accessKey, secretKey string) (*model.ConfigPage, error) {
return &model.ConfigPage{TotalCount: 1}, nil
}
func (m *MockConfigProxy) requestProxy(rpcClient *rpc.RpcClient, request rpc_request.IRequest, timeoutMills uint64) (rpc_response.IResponse, error) {
return &rpc_response.MockResponse{Response: &rpc_response.Response{Success: true}}, nil
}
func (m *MockConfigProxy) createRpcClient(ctx context.Context, taskId string, client *ConfigClient) *rpc.RpcClient {
return &rpc.RpcClient{}
}
func (m *MockConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient {
return &rpc.RpcClient{}
}
func Test_GetConfig(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: localConfigTest.DataId,
Group: localConfigTest.Group})
assert.Nil(t, err)
assert.Equal(t, "hello world!222222", content)
assert.Equal(t, "hello world", content)
}
func Test_GetConfigWithErrorResponse_401(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
result, err := client.GetConfig(configParamTest)
func Test_SearchConfig(t *testing.T) {
client := createConfigClientTest()
_, _ = client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
Content: "hello world"})
configPage, err := client.SearchConfig(vo.SearchConfigParam{
Search: "accurate",
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 10,
})
assert.Nil(t, err)
fmt.Printf("result:%s \n", result)
assert.NotEmpty(t, configPage)
}
func Test_GetConfigWithErrorResponse_404(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(404, ""), nil)
reslut, err := client.GetConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, "", reslut)
fmt.Println(err.Error())
}
func Test_GetConfigWithErrorResponse_403(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(403, ""), nil)
reslut, err := client.GetConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, "", reslut)
fmt.Println(err.Error())
}
func Test_GetConfigWithCache(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "content"), nil)
content, err := client.GetConfig(configParamTest)
func Test_GetConfigTls(t *testing.T) {
client := createConfigClientTestTls()
_, _ = client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
Content: "hello world"})
configPage, err := client.SearchConfig(vo.SearchConfigParam{
Search: "accurate",
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 10,
})
assert.Nil(t, err)
assert.Equal(t, "content", content)
assert.NotEmpty(t, configPage)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
content, err = client.GetConfig(configParamTest)
assert.Nil(t, err)
assert.Equal(t, "content", content)
}
// only using by ak sk for cipher config of aliyun kms
/*
func TestPublishAndGetConfigByUsingLocalCache(t *testing.T) {
param := vo.ConfigParam{
DataId: "cipher-kms-aes-256-usingCache" + strconv.Itoa(rand.Int()),
Group: "DEFAULT",
Content: "content加密&&" + strconv.Itoa(rand.Int()),
}
t.Run("PublishAndGetConfigByUsingLocalCache", func(t *testing.T) {
commonClient := createConfigClientCommon()
_, err := commonClient.PublishConfig(param)
assert.Nil(t, err)
time.Sleep(2 * time.Second)
configQueryContent, err := commonClient.GetConfig(param)
assert.Nil(t, err)
assert.Equal(t, param.Content, configQueryContent)
usingKmsCacheClient := createConfigClientForKms()
configQueryContentByUsingCache, err := usingKmsCacheClient.GetConfig(param)
assert.Nil(t, err)
assert.Equal(t, param.Content, configQueryContentByUsingCache)
newCipherContent := param.Content + "new"
param.Content = newCipherContent
err = commonClient.ListenConfig(vo.ConfigParam{
DataId: param.DataId,
Group: param.Group,
OnChange: func(namespace, group, dataId, data string) {
t.Log("origin data: " + newCipherContent + "; new data: " + data)
assert.Equal(t, newCipherContent, data)
},
})
assert.Nil(t, err)
result, err := commonClient.PublishConfig(param)
assert.Nil(t, err)
assert.True(t, result)
time.Sleep(2 * time.Second)
newContentCommon, err := commonClient.GetConfig(param)
assert.Nil(t, err)
assert.Equal(t, param.Content, newContentCommon)
newContentKms, err := usingKmsCacheClient.GetConfig(param)
assert.Nil(t, err)
assert.Equal(t, param.Content, newContentKms)
})
}
*/
// PublishConfig
func Test_PublishConfigWithoutDataId(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -224,20 +251,10 @@ func Test_PublishConfigWithoutDataId(t *testing.T) {
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutGroup(t *testing.T) {
client := cretateConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "",
Content: "content",
})
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutContent(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group",
Content: "",
})
@ -246,59 +263,25 @@ func Test_PublishConfigWithoutContent(t *testing.T) {
func Test_PublishConfig(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group",
Content: "hello world2!"})
SrcUser: "nacos-client-go",
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
}
func Test_PublishConfigWithErrorResponse(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodPost),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(localConfigMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
success, err := client.PublishConfig(localConfigTest)
assert.NotNil(t, err)
assert.True(t, !success)
}
func Test_PublishConfigWithErrorResponse_200(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodPost),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(localConfigMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "false"), nil)
success, err := client.PublishConfig(localConfigTest)
assert.NotNil(t, err)
assert.True(t, !success)
}
// DeleteConfig
func Test_DeleteConfig(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group",
Content: "hello world!"})
@ -306,51 +289,15 @@ func Test_DeleteConfig(t *testing.T) {
assert.True(t, success)
success, err = client.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group"})
assert.Nil(t, err)
assert.True(t, success)
}
func Test_DeleteConfigWithErrorResponse_200(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodDelete),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "false"), nil)
success, err := client.DeleteConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithErrorResponse_401(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodDelete),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
success, err := client.DeleteConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutDataId(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -359,296 +306,106 @@ func Test_DeleteConfigWithoutDataId(t *testing.T) {
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutGroup(t *testing.T) {
client := cretateConfigClientTest()
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
Group: "",
})
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
// ListenConfig
func TestListenConfig(t *testing.T) {
client := cretateConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!"})
assert.Nil(t, err)
assert.Equal(t, true, success)
content := ""
err = client.ListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
content = data
},
})
err = client.ListenConfig(vo.ConfigParam{
DataId: "abc",
Group: "DEFAULT_GROUP",
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
},
})
time.Sleep(5 * time.Second)
assert.Equal(t, "hello world!", content)
success, err = client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "abc"})
assert.Nil(t, err)
assert.Equal(t, true, success)
time.Sleep(10 * time.Second)
assert.Equal(t, "abc", content)
}
// listenConfigTask
func Test_listenConfigTask_NoChange(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Post(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs/listener"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, ""), nil)
changeCount := 0
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent, vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
OnChange: func(namespace, group, dataId, data string) {
changeCount = changeCount + 1
}})
assert.Equal(t, 0, changeCount)
}
func Test_listenConfigTask_Change_WithTenant(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
mockHttpAgent.EXPECT().Post(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs/listener"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "dataId%02group%02tenant%01"), nil)
mockHttpAgent.EXPECT().Get(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"dataId": "dataId",
"group": "group",
"tenant": "tenant",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "content2"), nil)
_ = client.SetHttpAgent(mockHttpAgent)
_ = client.SetClientConfig(clientConfigTest)
_ = client.SetServerConfig(serverConfigsTest)
configData := ""
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent, vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
OnChange: func(namespace, group, dataId, data string) {
configData = data
}})
assert.Equal(t, "content2", configData)
}
func Test_listenConfigTask_Change_NoTenant(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
mockHttpAgent.EXPECT().Post(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs/listener"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "dataId%02group%01"), nil)
mockHttpAgent.EXPECT().Get(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"dataId": "dataId",
"group": "group",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "content2"), nil)
_ = client.SetHttpAgent(mockHttpAgent)
_ = client.SetClientConfig(clientConfigTest)
_ = client.SetServerConfig(serverConfigsTest)
configData := ""
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent, vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
OnChange: func(namespace, group, dataId, data string) {
configData = data
},
})
assert.Equal(t, "content2", configData)
}
func Test_listenConfigTaskWithoutLocalConfigs(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent,
vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
func TestListen(t *testing.T) {
t.Run("TestListenConfig", func(t *testing.T) {
client := createConfigClientTest()
err := client.ListenConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
})
//assert.Equal(t, false, client.listening)
assert.Nil(t, err)
})
// ListenConfig no dataId
t.Run("TestListenConfigNoDataId", func(t *testing.T) {
listenConfigParam := vo.ConfigParam{
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
client := createConfigClientTest()
err := client.ListenConfig(listenConfigParam)
assert.Error(t, err)
})
}
// listen
// CancelListenConfig
func TestCancelListenConfig(t *testing.T) {
//Multiple listeners listen for different configurations, cancel one
t.Run("TestMultipleListenersCancelOne", func(t *testing.T) {
client := createConfigClientTest()
var err error
listenConfigParam := vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
func Test_listen(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
path := "http://console.nacos.io:80/nacos/v1/cs/configs/listener"
param := map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
listenConfigParam1 := vo.ConfigParam{
DataId: localConfigTest.DataId + "1",
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
_ = client.ListenConfig(listenConfigParam)
_ = client.ListenConfig(listenConfigParam1)
err = client.CancelListenConfig(listenConfigParam)
assert.Nil(t, err)
})
}
type MockAccessKeyCredentialProvider struct {
accessKey string
secretKey string
signatureRegionId string
}
func (provider *MockAccessKeyCredentialProvider) MatchProvider() bool {
return true
}
func (provider *MockAccessKeyCredentialProvider) Init() error {
return nil
}
func (provider *MockAccessKeyCredentialProvider) GetCredentialsForNacosClient() security.RamContext {
ramContext := security.RamContext{
AccessKey: provider.accessKey,
SecretKey: provider.secretKey,
SignatureRegionId: "",
}
changedString := "dataId%02group%02tenant%01"
mockHttpAgent.EXPECT().Post(
gomock.Eq(path),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(param),
).Times(1).Return(http_agent.FakeHttpResponse(200, changedString), nil)
_ = client.SetHttpAgent(mockHttpAgent)
changed, err := listen(mockHttpAgent, path, clientConfigTest.TimeoutMs, clientConfigTest.ListenInterval, param)
assert.Equal(t, changed, changedString)
assert.Nil(t, err)
return ramContext
}
func Test_listenWithErrorResponse_401(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
path := "http://console.nacos.io:80/nacos/v1/cs/configs/listener"
param := map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
func Test_ConfigClientWithProvider(t *testing.T) {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
clientConfigWithOptions.AccessKey = ""
clientConfigWithOptions.SecretKey = ""
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
provider := &MockAccessKeyCredentialProvider{
accessKey: "LTAxxx",
secretKey: "EdPxxx",
}
mockHttpAgent.EXPECT().Post(
gomock.Eq(path),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(param),
).Times(1).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
client, _ := NewConfigClientWithRamCredentialProvider(&nc, provider)
client.configProxy = &MockConfigProxy{}
success, err := client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: "hello world"})
_ = client.SetHttpAgent(mockHttpAgent)
_, err := listen(mockHttpAgent, path, clientConfigTest.TimeoutMs, clientConfigTest.ListenInterval, param)
assert.NotNil(t, err)
}
// AddConfigToListen
func Test_AddConfigToListenWithNotListening(t *testing.T) {
client := cretateConfigClientTest()
configs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
Content: "content",
}}
err := client.AddConfigToListen(configs)
assert.NotNil(t, err)
}
func Test_AddConfigToListenWithRepeatAdd(t *testing.T) {
client := cretateConfigClientTest()
configs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}}
addConfigs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}, {
DataId: "dataId2",
Group: "group",
}, {
DataId: "dataId3",
Group: "group",
}}
client.localConfigs = configs
err := client.AddConfigToListen(addConfigs)
assert.Nil(t, err)
assert.Equal(t, addConfigs, client.localConfigs)
}
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group})
func Test_AddConfigToListen(t *testing.T) {
client := cretateConfigClientTest()
configs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}}
addConfigs := []vo.ConfigParam{
{
DataId: "dataId2",
Group: "group",
}, {
DataId: "dataId3",
Group: "group",
}}
resultConfigs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}, {
DataId: "dataId2",
Group: "group",
}, {
DataId: "dataId3",
Group: "group",
}}
client.localConfigs = configs
err := client.AddConfigToListen(addConfigs)
assert.Nil(t, err)
assert.Equal(t, resultConfigs, client.localConfigs)
assert.Equal(t, "hello world", content)
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"strconv"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
)
type ConfigConnectionEventListener struct {
client *ConfigClient
taskId string
}
func NewConfigConnectionEventListener(client *ConfigClient, taskId string) *ConfigConnectionEventListener {
return &ConfigConnectionEventListener{
client: client,
taskId: taskId,
}
}
func (c *ConfigConnectionEventListener) OnConnected() {
logger.Info("[ConfigConnectionEventListener] connect to config server for taskId: " + c.taskId)
if c.client != nil {
c.client.asyncNotifyListenConfig()
}
}
func (c *ConfigConnectionEventListener) OnDisConnect() {
logger.Info("[ConfigConnectionEventListener] disconnect from config server for taskId: " + c.taskId)
if c.client != nil {
taskIdInt, err := strconv.Atoi(c.taskId)
if err != nil {
logger.Errorf("[ConfigConnectionEventListener] parse taskId error: %v", err)
return
}
items := c.client.cacheMap.Items()
for key, v := range items {
if data, ok := v.(cacheData); ok {
if data.taskId == taskIdInt {
data.isSyncWithServer = false
c.client.cacheMap.Set(key, data)
}
}
}
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"context"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/stretchr/testify/assert"
)
func TestNewConfigConnectionEventListener(t *testing.T) {
client := &ConfigClient{}
taskId := "123"
listener := NewConfigConnectionEventListener(client, taskId)
assert.Equal(t, client, listener.client)
assert.Equal(t, taskId, listener.taskId)
}
func TestOnDisConnectWithMock(t *testing.T) {
client := &ConfigClient{
cacheMap: cache.NewConcurrentMap(),
}
data1 := cacheData{
dataId: "dataId1",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
data2 := cacheData{
dataId: "dataId2",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
data3 := cacheData{
dataId: "dataId3",
group: "group2",
tenant: "",
taskId: 2,
isSyncWithServer: true,
}
key1 := util.GetConfigCacheKey(data1.dataId, data1.group, data1.tenant)
key2 := util.GetConfigCacheKey(data2.dataId, data2.group, data2.tenant)
key3 := util.GetConfigCacheKey(data3.dataId, data3.group, data3.tenant)
client.cacheMap.Set(key1, data1)
client.cacheMap.Set(key2, data2)
client.cacheMap.Set(key3, data3)
listener := NewConfigConnectionEventListener(client, "1")
listener.OnDisConnect()
item1, _ := client.cacheMap.Get(key1)
item2, _ := client.cacheMap.Get(key2)
item3, _ := client.cacheMap.Get(key3)
updatedData1 := item1.(cacheData)
updatedData2 := item2.(cacheData)
updatedData3 := item3.(cacheData)
assert.False(t, updatedData1.isSyncWithServer, "dataId1 should be marked as not sync")
assert.False(t, updatedData2.isSyncWithServer, "dataId2 should be marked as not sync")
assert.True(t, updatedData3.isSyncWithServer, "dataId3 should be marked as sync")
}
func TestOnConnectedWithMock(t *testing.T) {
listenChan := make(chan struct{}, 1)
client := &ConfigClient{
listenExecute: listenChan,
}
listener := NewConfigConnectionEventListener(client, "1")
listener.OnConnected()
time.Sleep(100 * time.Millisecond)
select {
case <-listenChan:
assert.True(t, true, "asyncNotifyListenConfig should be called")
default:
t.Fatalf("asyncNotifyListenConfig should be called but not")
}
}
type MockRpcClientForListener struct {
requestCalled rpc_request.IRequest
}
func (m *MockRpcClientForListener) Request(request rpc_request.IRequest) (rpc_response.IResponse, error) {
m.requestCalled = request
return &rpc_response.ConfigChangeBatchListenResponse{
Response: &rpc_response.Response{
ResultCode: 200,
},
ChangedConfigs: []model.ConfigContext{},
}, nil
}
func TestReconnectionFlow(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mockRpc := &MockRpcClientForListener{}
listenChan := make(chan struct{}, 1)
client := &ConfigClient{
ctx: ctx,
configProxy: &MockConfigProxy{},
cacheMap: cache.NewConcurrentMap(),
listenExecute: listenChan,
}
done := make(chan bool)
go func() {
for {
select {
case <-listenChan:
mockRpc.Request(&rpc_request.ConfigBatchListenRequest{})
done <- true
case <-ctx.Done():
return
}
}
}()
data1 := cacheData{
dataId: "dataId1",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
key1 := util.GetConfigCacheKey(data1.dataId, data1.group, data1.tenant)
client.cacheMap.Set(key1, data1)
listener := NewConfigConnectionEventListener(client, "1")
initialData, _ := client.cacheMap.Get(key1)
assert.True(t, initialData.(cacheData).isSyncWithServer, "initial data should be sync with server")
listener.OnDisConnect()
afterDisconnectData, _ := client.cacheMap.Get(key1)
assert.False(t, afterDisconnectData.(cacheData).isSyncWithServer, "disconnect should set isSyncWithServer to false")
listener.OnConnected()
select {
case <-done:
case <-time.After(1 * time.Second):
t.Fatalf("wait for done timeout")
}
assert.NotNil(t, mockRpc.requestCalled, "should call request")
_, ok := mockRpc.requestCalled.(*rpc_request.ConfigBatchListenRequest)
assert.True(t, ok, "should be a ConfigBatchListenRequest")
}

View File

@ -1,81 +1,232 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/common/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"context"
"encoding/json"
"net/http"
"strings"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/pkg/errors"
)
type ConfigProxy struct {
nacosServer nacos_server.NacosServer
nacosServer *nacos_server.NacosServer
clientConfig constant.ClientConfig
}
func NewConfigProxy(serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent) (ConfigProxy, error) {
func NewConfigProxy(ctx context.Context, serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent) (IConfigProxy, error) {
return NewConfigProxyWithRamCredentialProvider(ctx, serverConfig, clientConfig, httpAgent, nil)
}
func NewConfigProxyWithRamCredentialProvider(ctx context.Context, serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent, provider security.RamCredentialProvider) (IConfigProxy, error) {
proxy := ConfigProxy{}
var err error
proxy.nacosServer, err = nacos_server.NewNacosServer(serverConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint)
return proxy, err
proxy.nacosServer, err = nacos_server.NewNacosServerWithRamCredentialProvider(ctx, serverConfig, clientConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint, nil, provider)
proxy.clientConfig = clientConfig
return &proxy, err
}
func (cp *ConfigProxy) GetServerList() []constant.ServerConfig {
return cp.nacosServer.GetServerList()
func (cp *ConfigProxy) requestProxy(rpcClient *rpc.RpcClient, request rpc_request.IRequest, timeoutMills uint64) (rpc_response.IResponse, error) {
start := time.Now()
cp.nacosServer.InjectSecurityInfo(request.GetHeaders(), security.BuildConfigResourceByRequest(request))
cp.injectCommHeader(request.GetHeaders())
response, err := rpcClient.Request(request, int64(timeoutMills))
monitor.GetConfigRequestMonitor(constant.GRPC, request.GetRequestType(), rpc_response.GetGrpcResponseStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
return response, err
}
func (cp *ConfigProxy) GetConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (string, error) {
func (cp *ConfigProxy) injectCommHeader(param map[string]string) {
now := strconv.FormatInt(util.CurrentMillis(), 10)
param[constant.CLIENT_APPNAME_HEADER] = cp.clientConfig.AppName
param[constant.CLIENT_REQUEST_TS_HEADER] = now
param[constant.CLIENT_REQUEST_TOKEN_HEADER] = util.Md5(now + cp.clientConfig.AppKey)
param[constant.EX_CONFIG_INFO] = "true"
param[constant.CHARSET_KEY] = "utf-8"
}
func (cp *ConfigProxy) searchConfigProxy(param vo.SearchConfigParam, tenant, accessKey, secretKey string) (*model.ConfigPage, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodGet)
return result, err
}
func (cp *ConfigProxy) PublishConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
if _, ok := params["group"]; !ok {
params["group"] = ""
}
if _, ok := params["dataId"]; !ok {
params["dataId"] = ""
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodPost)
var version = "v2"
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.PublishConfig] publish config failed:" + err.Error())
if len(tenant) > 0 {
params["namespaceId"] = params["tenant"]
}
params["groupName"] = params["group"]
result, err = cp.nacosServer.ReqConfigApi("/v3/admin/cs/config/list", params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
if err != nil {
return nil, err
}
version = "v3"
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
var configPage model.ConfigPage
if version == "v2" {
err = json.Unmarshal([]byte(result), &configPage)
} else {
return false, errors.New("[client.PublishConfig] publish config failed:" + string(result))
var configPageResult model.ConfigPageResult
err = json.Unmarshal([]byte(result), &configPageResult)
configPage = configPageResult.Data
}
if err != nil {
return nil, err
}
return &configPage, nil
}
func (cp *ConfigProxy) DeleteConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
func (cp *ConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
if group == "" {
group = constant.DEFAULT_GROUP
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodDelete)
configQueryRequest := rpc_request.NewConfigQueryRequest(group, dataId, tenant)
configQueryRequest.Headers["notify"] = strconv.FormatBool(notify)
cacheKey := util.GetConfigCacheKey(dataId, group, tenant)
// use the same key of config file as the limit checker's key
if IsLimited(cacheKey) {
// return error when check limited
return nil, errors.New("ConfigQueryRequest is limited")
}
iResponse, err := cp.requestProxy(cp.getRpcClient(client), configQueryRequest, timeout)
if err != nil {
return false, errors.New("[client.DeleteConfig] deleted config failed:" + err.Error())
return nil, err
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
} else {
return false, errors.New("[client.DeleteConfig] deleted config failed: " + string(result))
response, ok := iResponse.(*rpc_response.ConfigQueryResponse)
if !ok {
return nil, errors.New("ConfigQueryRequest returns type error")
}
if response.IsSuccess() {
cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, response.Content)
cache.WriteEncryptedDataKeyToFile(cacheKey, cp.clientConfig.CacheDir, response.EncryptedDataKey)
if response.ContentType == "" {
response.ContentType = "text"
}
return response, nil
}
if response.GetErrorCode() == 300 {
cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, "")
cache.WriteEncryptedDataKeyToFile(cacheKey, cp.clientConfig.CacheDir, "")
response.SetSuccess(true)
return response, nil
}
if response.GetErrorCode() == 400 {
logger.Errorf(
"[config_rpc_client] [sub-server-error] get server config being modified concurrently, dataId=%s, group=%s, "+
"tenant=%s", dataId, group, tenant)
return nil, errors.New("data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant)
}
if response.GetErrorCode() > 0 {
logger.Errorf("[config_rpc_client] [sub-server-error] dataId=%s, group=%s, tenant=%s, code=%+v", dataId, group,
tenant, response)
}
return response, nil
}
func appName(client *ConfigClient) string {
if clientConfig, err := client.GetClientConfig(); err == nil {
appName := clientConfig.AppName
return appName
}
return "unknown"
}
func (cp *ConfigProxy) createRpcClient(ctx context.Context, taskId string, client *ConfigClient) *rpc.RpcClient {
labels := map[string]string{
constant.LABEL_SOURCE: constant.LABEL_SOURCE_SDK,
constant.LABEL_MODULE: constant.LABEL_MODULE_CONFIG,
constant.APPNAME_HEADER: appName(client),
"taskId": taskId,
}
iRpcClient, _ := rpc.CreateClient(ctx, "config-"+taskId+"-"+client.uid, rpc.GRPC, labels, cp.nacosServer, &cp.clientConfig.TLSCfg, cp.clientConfig.AppConnLabels)
rpcClient := iRpcClient.GetRpcClient()
if rpcClient.IsInitialized() {
rpcClient.RegisterServerRequestHandler(func() rpc_request.IRequest {
// TODO fix the group/dataId empty problem
return rpc_request.NewConfigChangeNotifyRequest("", "", "")
}, &ConfigChangeNotifyRequestHandler{client: client})
configListener := NewConfigConnectionEventListener(client, taskId)
rpcClient.RegisterConnectionListener(configListener)
rpcClient.Tenant = cp.clientConfig.NamespaceId
rpcClient.Start()
}
return rpcClient
}
func (cp *ConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient {
return cp.createRpcClient(client.ctx, "0", client)
}
type ConfigChangeNotifyRequestHandler struct {
client *ConfigClient
}
func (c *ConfigChangeNotifyRequestHandler) Name() string {
return "ConfigChangeNotifyRequestHandler"
}
func (c *ConfigChangeNotifyRequestHandler) RequestReply(request rpc_request.IRequest, rpcClient *rpc.RpcClient) rpc_response.IResponse {
configChangeNotifyRequest, ok := request.(*rpc_request.ConfigChangeNotifyRequest)
if !ok {
return nil
}
logger.Infof("%s [server-push] config changed. dataId=%s, group=%s,tenant=%s", rpcClient.Name(),
configChangeNotifyRequest.DataId, configChangeNotifyRequest.Group, configChangeNotifyRequest.Tenant)
cacheKey := util.GetConfigCacheKey(configChangeNotifyRequest.DataId, configChangeNotifyRequest.Group,
configChangeNotifyRequest.Tenant)
data, ok := c.client.cacheMap.Get(cacheKey)
if !ok {
return nil
}
cData := data.(cacheData)
cData.isSyncWithServer = false
c.client.cacheMap.Set(cacheKey, cData)
c.client.asyncNotifyListenConfig()
return &rpc_response.NotifySubscriberResponse{
Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS},
}
}

View File

@ -0,0 +1,19 @@
package config_client
import (
"context"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
type IConfigProxy interface {
queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error)
searchConfigProxy(param vo.SearchConfigParam, tenant, accessKey, secretKey string) (*model.ConfigPage, error)
requestProxy(rpcClient *rpc.RpcClient, request rpc_request.IRequest, timeoutMills uint64) (rpc_response.IResponse, error)
createRpcClient(ctx context.Context, taskId string, client *ConfigClient) *rpc.RpcClient
getRpcClient(client *ConfigClient) *rpc.RpcClient
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"golang.org/x/time/rate"
)
type rateLimiterCheck struct {
rateLimiterCache cache.ConcurrentMap // cache
mux sync.Mutex
}
var checker rateLimiterCheck
func init() {
checker = rateLimiterCheck{
rateLimiterCache: cache.NewConcurrentMap(),
mux: sync.Mutex{},
}
}
// IsLimited return true when request is limited
func IsLimited(checkKey string) bool {
checker.mux.Lock()
defer checker.mux.Unlock()
var limiter *rate.Limiter
lm, exist := checker.rateLimiterCache.Get(checkKey)
if !exist {
// define a new limiter,allow 5 times per second,and reserve stock is 5.
limiter = rate.NewLimiter(rate.Limit(5), 5)
checker.rateLimiterCache.Set(checkKey, limiter)
} else {
limiter = lm.(*rate.Limiter)
}
add := time.Now().Add(time.Second)
return !limiter.AllowN(add, 1)
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 1999-2020 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.
*/
package config_client
import (
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
func TestLimiter(t *testing.T) {
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "default-group",
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
for i := 0; i <= 10; i++ {
content, err := client.GetConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "default-group"})
if i > 4 {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
assert.Equal(t, "hello world", content)
}
}
}

View File

@ -1,23 +1,31 @@
/*
* Copyright 1999-2020 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.
*/
package nacos_client
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/utils"
"log"
"os"
"strconv"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 16:39
**/
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
)
type NacosClient struct {
clientConfigValid bool
@ -27,46 +35,43 @@ type NacosClient struct {
serverConfigs []constant.ServerConfig
}
// 设置 clientConfig
// SetClientConfig is use to set nacos client Config
func (client *NacosClient) SetClientConfig(config constant.ClientConfig) (err error) {
if config.TimeoutMs <= 0 {
err = errors.New("[client.SetClientConfig] config.TimeoutMs should > 0")
return
}
if config.TimeoutMs >= config.ListenInterval {
err = errors.New("[client.SetClientConfig] config.TimeoutMs should < config.ListenInterval")
return
config.TimeoutMs = 10 * 1000
}
if config.BeatInterval <= 0 {
config.BeatInterval = 5 * 1000
}
if config.ListenInterval < 10*1000 {
config.ListenInterval = 10 * 1000
}
if config.UpdateThreadNum <= 0 {
config.UpdateThreadNum = 20
}
if len(config.LogLevel) == 0 {
config.LogLevel = "info"
}
if config.CacheDir == "" {
config.CacheDir = utils.GetCurrentPath() + string(os.PathSeparator) + "cache"
config.CacheDir = file.GetCurrentPath() + string(os.PathSeparator) + "cache"
}
if config.LogDir == "" {
config.LogDir = utils.GetCurrentPath() + string(os.PathSeparator) + "log"
config.LogDir = file.GetCurrentPath() + string(os.PathSeparator) + "log"
}
log.Printf("[INFO] logDir:<%s> cacheDir:<%s>", config.LogDir, config.CacheDir)
client.clientConfig = config
client.clientConfigValid = true
return
}
// 设置 serverConfigs
// SetServerConfig is use to set nacos server config
func (client *NacosClient) SetServerConfig(configs []constant.ServerConfig) (err error) {
if len(configs) <= 0 {
//it's may be use endpoint to get nacos server address
client.serverConfigsValid = true
//err = errors.New("[client.SetServerConfig] configs can not be empty")
return
}
@ -78,13 +83,16 @@ func (client *NacosClient) SetServerConfig(configs []constant.ServerConfig) (err
if len(configs[i].ContextPath) <= 0 {
configs[i].ContextPath = constant.DEFAULT_CONTEXT_PATH
}
if len(configs[i].Scheme) <= 0 {
configs[i].Scheme = constant.DEFAULT_SERVER_SCHEME
}
}
client.serverConfigs = configs
client.serverConfigsValid = true
return
}
// 获取 clientConfig
// GetClientConfig use to get client config
func (client *NacosClient) GetClientConfig() (config constant.ClientConfig, err error) {
config = client.clientConfig
if !client.clientConfigValid {
@ -93,7 +101,7 @@ func (client *NacosClient) GetClientConfig() (config constant.ClientConfig, err
return
}
// 获取serverConfigs
// GetServerConfig use to get server config
func (client *NacosClient) GetServerConfig() (configs []constant.ServerConfig, err error) {
configs = client.serverConfigs
if !client.serverConfigsValid {
@ -102,6 +110,7 @@ func (client *NacosClient) GetServerConfig() (configs []constant.ServerConfig, e
return
}
// SetHttpAgent use to set http agent
func (client *NacosClient) SetHttpAgent(agent http_agent.IHttpAgent) (err error) {
if agent == nil {
err = errors.New("[client.SetHttpAgent] http agent can not be nil")
@ -111,6 +120,7 @@ func (client *NacosClient) SetHttpAgent(agent http_agent.IHttpAgent) (err error)
return
}
// GetHttpAgent use to get http agent
func (client *NacosClient) GetHttpAgent() (agent http_agent.IHttpAgent, err error) {
if client.agent == nil {
err = errors.New("[client.GetHttpAgent] invalid http agent")

View File

@ -1,26 +1,40 @@
/*
* Copyright 1999-2020 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.
*/
package nacos_client
import (
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 16:32
**/
//go:generate mockgen -destination mock_nacos_client_interface.go -package nacos_client -source=./nacos_client_interface.go
type INacosClient interface {
//SetClientConfig is use to set nacos client config
SetClientConfig(constant.ClientConfig) error
//SetServerConfig is use to set nacos server config
SetServerConfig([]constant.ServerConfig) error
//GetClientConfig use to get client config
GetClientConfig() (constant.ClientConfig, error)
//GetServerConfig use to get server config
GetServerConfig() ([]constant.ServerConfig, error)
//SetHttpAgent use to set http agent
SetHttpAgent(http_agent.IHttpAgent) error
//GetHttpAgent use to get http agent
GetHttpAgent() (http_agent.IHttpAgent, error)
}

View File

@ -1,90 +0,0 @@
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
nsema "github.com/toolkits/concurrent/semaphore"
"log"
"strconv"
"time"
)
type BeatReactor struct {
beatMap cache.ConcurrentMap
serviceProxy NamingProxy
clientBeatInterval int64
beatThreadCount int
beatThreadSemaphore *nsema.Semaphore
beatRecordMap cache.ConcurrentMap
}
const Default_Beat_Thread_Num = 20
func NewBeatReactor(serviceProxy NamingProxy, clientBeatInterval int64) BeatReactor {
br := BeatReactor{}
if clientBeatInterval <= 0 {
clientBeatInterval = 5 * 1000
}
br.beatMap = cache.NewConcurrentMap()
br.serviceProxy = serviceProxy
br.clientBeatInterval = clientBeatInterval
br.beatThreadCount = Default_Beat_Thread_Num
br.beatRecordMap = cache.NewConcurrentMap()
br.beatThreadSemaphore = nsema.NewSemaphore(br.beatThreadCount)
return br
}
func buildKey(serviceName string, ip string, port uint64) string {
return serviceName + constant.NAMING_INSTANCE_ID_SPLITTER + ip + constant.NAMING_INSTANCE_ID_SPLITTER + strconv.Itoa(int(port))
}
func (br *BeatReactor) AddBeatInfo(serviceName string, beatInfo model.BeatInfo) {
log.Printf("[INFO] adding beat: <%s> to beat map.\n", utils.ToJsonString(beatInfo))
k := buildKey(serviceName, beatInfo.Ip, beatInfo.Port)
br.beatMap.Set(k, beatInfo)
go br.sendInstanceBeat(k, &beatInfo)
}
func (br *BeatReactor) RemoveBeatInfo(serviceName string, ip string, port uint64) {
log.Printf("[INFO] remove beat: %s@%s:%d from beat map.\n", serviceName, ip, port)
k := buildKey(serviceName, ip, port)
data, exist := br.beatMap.Get(k)
if exist {
beatInfo := data.(*model.BeatInfo)
beatInfo.Stopped = true
}
br.beatMap.Remove(k)
}
func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
for {
br.beatThreadSemaphore.Acquire()
//进行心跳通信
beatInterval, err := br.serviceProxy.SendBeat(*beatInfo)
if err != nil {
log.Printf("[ERROR]:beat to server return error:%s \n", err.Error())
br.beatThreadSemaphore.Release()
t := time.NewTimer(beatInfo.Period)
<-t.C
continue
}
if beatInterval > 0 {
beatInfo.Period = time.Duration(time.Millisecond.Nanoseconds() * beatInterval)
}
//如果当前实例注销,则进行停止心跳
if beatInfo.Stopped {
log.Printf("[INFO] intance[%s] stop heartBeating\n", k)
br.beatThreadSemaphore.Release()
return
}
br.beatRecordMap.Set(k, utils.CurrentMillis())
br.beatThreadSemaphore.Release()
t := time.NewTimer(beatInfo.Period)
<-t.C
}
}

View File

@ -1,58 +0,0 @@
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/stretchr/testify/assert"
"testing"
)
func TestBeatReactor_AddBeatInfo(t *testing.T) {
br := NewBeatReactor(NamingProxy{}, 5000)
serviceName := "Test"
groupName := "public"
beatInfo := model.BeatInfo{
Ip: "127.0.0.1",
Port: 8080,
Metadata: map[string]string{},
ServiceName: utils.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(utils.GetGroupName(serviceName, groupName), beatInfo)
key := buildKey(utils.GetGroupName(serviceName, groupName), beatInfo.Ip, beatInfo.Port)
result, ok := br.beatMap.Get(key)
assert.Equal(t, ok, true, "key should exists!")
assert.ObjectsAreEqual(result.(model.BeatInfo), beatInfo)
}
func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
br := NewBeatReactor(NamingProxy{}, 5000)
serviceName := "Test"
groupName := "public"
beatInfo1 := model.BeatInfo{
Ip: "127.0.0.1",
Port: 8080,
Metadata: map[string]string{},
ServiceName: utils.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(utils.GetGroupName(serviceName, groupName), beatInfo1)
beatInfo2 := model.BeatInfo{
Ip: "127.0.0.2",
Port: 8080,
Metadata: map[string]string{},
ServiceName: utils.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(utils.GetGroupName(serviceName, groupName), beatInfo2)
br.RemoveBeatInfo(utils.GetGroupName(serviceName, groupName), "127.0.0.1", 8080)
key := buildKey(utils.GetGroupName(serviceName, groupName), beatInfo2.Ip, beatInfo2.Port)
result, ok := br.beatMap.Get(key)
assert.Equal(t, br.beatMap.Count(), 1, "beatinfo map length should be 1")
assert.Equal(t, ok, true, "key should exists!")
assert.ObjectsAreEqual(result.(model.BeatInfo), beatInfo2)
}

View File

@ -1,9 +0,0 @@
package naming_client
import (
"testing"
)
func TestHostReactor_GetServiceInfo(t *testing.T) {
}

View File

@ -1,153 +0,0 @@
package naming_client
import (
"encoding/json"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
nsema "github.com/toolkits/concurrent/semaphore"
"log"
"reflect"
"time"
)
type HostReactor struct {
serviceInfoMap cache.ConcurrentMap
cacheDir string
updateThreadNum int
serviceProxy NamingProxy
pushReceiver PushReceiver
subCallback SubscribeCallback
updateTimeMap cache.ConcurrentMap
updateCacheWhenEmpty bool
}
const Default_Update_Thread_Num = 20
func NewHostReactor(serviceProxy NamingProxy, cacheDir string, updateThreadNum int, notLoadCacheAtStart bool, subCallback SubscribeCallback, updateCacheWhenEmpty bool) HostReactor {
if updateThreadNum <= 0 {
updateThreadNum = Default_Update_Thread_Num
}
hr := HostReactor{
serviceProxy: serviceProxy,
cacheDir: cacheDir,
updateThreadNum: updateThreadNum,
serviceInfoMap: cache.NewConcurrentMap(),
subCallback: subCallback,
updateTimeMap: cache.NewConcurrentMap(),
updateCacheWhenEmpty: updateCacheWhenEmpty,
}
pr := NewPushRecevier(&hr)
hr.pushReceiver = *pr
if !notLoadCacheAtStart {
hr.loadCacheFromDisk()
}
go hr.asyncUpdateService()
return hr
}
func (hr *HostReactor) loadCacheFromDisk() {
serviceMap := cache.ReadServicesFromFile(hr.cacheDir)
if serviceMap == nil || len(serviceMap) == 0 {
return
}
for k, v := range serviceMap {
hr.serviceInfoMap.Set(k, v)
}
}
func (hr *HostReactor) ProcessServiceJson(result string) {
service := utils.JsonToService(result)
if service == nil {
return
}
cacheKey := utils.GetServiceCacheKey(service.Name, service.Clusters)
oldDomain, ok := hr.serviceInfoMap.Get(cacheKey)
if ok && !hr.updateCacheWhenEmpty {
//if instance list is empty,not to update cache
if len(result) == 0 {
log.Printf("[ERROR]:do not have useful host, ignore it, name:%s \n", service.Name)
return
}
}
if !ok || ok && !reflect.DeepEqual(service.Hosts, oldDomain.(model.Service).Hosts) {
if !ok {
log.Println("[INFO] service not found in cache " + cacheKey)
} else {
log.Printf("[INFO] service key:%s was updated to:%s \n", cacheKey, utils.ToJsonString(service))
}
cache.WriteServicesToFile(*service, hr.cacheDir)
hr.subCallback.ServiceChanged(service)
}
hr.updateTimeMap.Set(cacheKey, uint64(utils.CurrentMillis()))
hr.serviceInfoMap.Set(cacheKey, *service)
}
func (hr *HostReactor) GetServiceInfo(serviceName string, clusters string) model.Service {
key := utils.GetServiceCacheKey(serviceName, clusters)
cacheService, ok := hr.serviceInfoMap.Get(key)
if !ok {
cacheService = model.Service{Name: serviceName, Clusters: clusters}
hr.serviceInfoMap.Set(key, cacheService)
hr.updateServiceNow(serviceName, clusters)
}
newService, _ := hr.serviceInfoMap.Get(key)
return newService.(model.Service)
}
func (hr *HostReactor) GetAllServiceInfo(nameSpace string, groupName string, clusters string) []model.Service {
result, err := hr.serviceProxy.GetAllServiceInfoList(nameSpace, groupName, clusters)
if err != nil {
log.Printf("[ERROR]:query all services info return error!nameSpace:%s cluster:%s groupName:%s err:%s \n", nameSpace, clusters, groupName, err.Error())
return nil
}
if result == "" {
log.Printf("[ERROR]:query all services info is empty!nameSpace:%s cluster:%s groupName:%s \n", nameSpace, clusters, groupName)
return nil
}
var data []model.Service
err = json.Unmarshal([]byte(result), &data)
if err != nil {
log.Printf("[ERROR]: the result of quering all services info json.Unmarshal error !nameSpace:%s cluster:%s groupName:%s \n", nameSpace, clusters, groupName)
return nil
}
return data
}
func (hr *HostReactor) updateServiceNow(serviceName string, clusters string) {
result, err := hr.serviceProxy.QueryList(serviceName, clusters, hr.pushReceiver.port, false)
if err != nil {
log.Printf("[ERROR]:query list return error!servieName:%s cluster:%s err:%s \n", serviceName, clusters, err.Error())
return
}
if result == "" {
log.Printf("[ERROR]:query list is empty!servieName:%s cluster:%s \n", serviceName, clusters)
return
}
hr.ProcessServiceJson(result)
}
func (hr *HostReactor) asyncUpdateService() {
sema := nsema.NewSemaphore(hr.updateThreadNum)
for {
for _, v := range hr.serviceInfoMap.Items() {
service := v.(model.Service)
lastRefTime, ok := hr.updateTimeMap.Get(utils.GetServiceCacheKey(service.Name, service.Clusters))
if !ok {
lastRefTime = uint64(0)
}
if uint64(utils.CurrentMillis())-lastRefTime.(uint64) > service.CacheMillis {
sema.Acquire()
go func() {
hr.updateServiceNow(service.Name, service.Clusters)
sema.Release()
}()
}
}
time.Sleep(1 * time.Second)
}
}

View File

@ -0,0 +1,191 @@
/*
* Copyright 1999-2020 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.
*/
package naming_cache
import (
"os"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type ServiceInfoHolder struct {
ServiceInfoMap sync.Map
updateCacheWhenEmpty bool
cacheDir string
notLoadCacheAtStart bool
subCallback *SubscribeCallback
UpdateTimeMap sync.Map
}
func NewServiceInfoHolder(namespace, cacheDir string, updateCacheWhenEmpty, notLoadCacheAtStart bool) *ServiceInfoHolder {
cacheDir = cacheDir + string(os.PathSeparator) + "naming" + string(os.PathSeparator) + namespace
serviceInfoHolder := &ServiceInfoHolder{
updateCacheWhenEmpty: updateCacheWhenEmpty,
notLoadCacheAtStart: notLoadCacheAtStart,
cacheDir: cacheDir,
subCallback: NewSubscribeCallback(),
UpdateTimeMap: sync.Map{},
ServiceInfoMap: sync.Map{},
}
if !notLoadCacheAtStart {
serviceInfoHolder.loadCacheFromDisk()
}
return serviceInfoHolder
}
func (s *ServiceInfoHolder) loadCacheFromDisk() {
serviceMap := cache.ReadServicesFromFile(s.cacheDir)
if serviceMap == nil || len(serviceMap) == 0 {
return
}
for k, v := range serviceMap {
s.ServiceInfoMap.Store(k, v)
}
}
func (s *ServiceInfoHolder) ProcessServiceJson(data string) {
s.ProcessService(util.JsonToService(data))
}
func (s *ServiceInfoHolder) ProcessService(service *model.Service) {
if service == nil {
return
}
if !s.updateCacheWhenEmpty {
//if instance list is empty,not to update cache
if service.Hosts == nil || len(service.Hosts) == 0 {
logger.Warnf("instance list is empty, updateCacheWhenEmpty is set to false, callback is not triggered. service name:%s", service.Name)
return
}
}
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
oldDomain, ok := s.ServiceInfoMap.Load(cacheKey)
if ok && oldDomain.(model.Service).LastRefTime >= service.LastRefTime {
logger.Warnf("out of date data received, old-t: %d, new-t: %d", oldDomain.(model.Service).LastRefTime, service.LastRefTime)
return
}
s.UpdateTimeMap.Store(cacheKey, uint64(util.CurrentMillis()))
s.ServiceInfoMap.Store(cacheKey, *service)
if !ok || checkInstanceChanged(oldDomain, *service) {
logger.Infof("service key:%s was updated to:%s", cacheKey, util.ToJsonString(service))
cache.WriteServicesToFile(service, cacheKey, s.cacheDir)
s.subCallback.ServiceChanged(cacheKey, service)
}
var count int
s.ServiceInfoMap.Range(func(key, value interface{}) bool {
count++
return true
})
monitor.GetServiceInfoMapSizeMonitor().Set(float64(count))
}
func (s *ServiceInfoHolder) GetServiceInfo(serviceName, groupName, clusters string) (model.Service, bool) {
cacheKey := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
//todo FailoverReactor
service, ok := s.ServiceInfoMap.Load(cacheKey)
if ok {
return service.(model.Service), ok
}
return model.Service{}, ok
}
func (s *ServiceInfoHolder) RegisterCallback(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
s.subCallback.AddCallbackFunc(serviceName, clusters, callbackWrapper)
}
func (s *ServiceInfoHolder) DeregisterCallback(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
s.subCallback.RemoveCallbackFunc(serviceName, clusters, callbackWrapper)
}
func (s *ServiceInfoHolder) StopUpdateIfContain(serviceName, clusters string) {
cacheKey := util.GetServiceCacheKey(serviceName, clusters)
s.ServiceInfoMap.Delete(cacheKey)
}
func (s *ServiceInfoHolder) IsSubscribed(serviceName, clusters string) bool {
return s.subCallback.IsSubscribed(serviceName, clusters)
}
func checkInstanceChanged(oldDomain interface{}, service model.Service) bool {
if oldDomain == nil {
return true
}
oldService := oldDomain.(model.Service)
return isServiceInstanceChanged(oldService, service)
}
// return true when service instance changed ,otherwise return false.
func isServiceInstanceChanged(oldService, newService model.Service) bool {
oldHostsLen := len(oldService.Hosts)
newHostsLen := len(newService.Hosts)
if oldHostsLen != newHostsLen {
return true
}
// compare refTime
oldRefTime := oldService.LastRefTime
newRefTime := newService.LastRefTime
if oldRefTime > newRefTime {
logger.Warnf("out of date data received, old-t: %v , new-t: %v", oldRefTime, newRefTime)
return false
}
// sort instance list
oldInstance := oldService.Hosts
newInstance := make([]model.Instance, len(newService.Hosts))
copy(newInstance, newService.Hosts)
sortInstance(oldInstance)
sortInstance(newInstance)
return !reflect.DeepEqual(oldInstance, newInstance)
}
type instanceSorter []model.Instance
func (s instanceSorter) Len() int {
return len(s)
}
func (s instanceSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s instanceSorter) Less(i, j int) bool {
insI, insJ := s[i], s[j]
// using ip and port to sort
ipNum1, _ := strconv.Atoi(strings.ReplaceAll(insI.Ip, ".", ""))
ipNum2, _ := strconv.Atoi(strings.ReplaceAll(insJ.Ip, ".", ""))
if ipNum1 < ipNum2 {
return true
}
if insI.Port < insJ.Port {
return true
}
return false
}
// sort instances
func sortInstance(instances []model.Instance) {
sort.Sort(instanceSorter(instances))
}

View File

@ -0,0 +1,152 @@
/*
* Copyright 1999-2020 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.
*/
package naming_cache
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/stretchr/testify/assert"
)
func TestServiceInfoHolder_isServiceInstanceChanged(t *testing.T) {
rand.Seed(time.Now().Unix())
defaultIp := createRandomIp()
defaultPort := creatRandomPort()
serviceA := model.Service{
LastRefTime: 1000,
Hosts: []model.Instance{
{
Ip: defaultIp,
Port: defaultPort,
},
{
Ip: defaultIp,
Port: defaultPort + 1,
},
{
Ip: defaultIp,
Port: defaultPort + 2,
},
},
}
serviceB := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: defaultIp,
Port: defaultPort,
},
{
Ip: defaultIp,
Port: defaultPort + 3,
},
{
Ip: defaultIp,
Port: defaultPort + 4,
},
},
}
ip := createRandomIp()
serviceC := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: ip,
Port: defaultPort,
},
{
Ip: ip,
Port: defaultPort + 3,
},
{
Ip: ip,
Port: defaultPort + 4,
},
},
}
t.Run("compareWithSelf", func(t *testing.T) {
changed := isServiceInstanceChanged(serviceA, serviceA)
assert.Equal(t, false, changed)
})
// compareWithIp
t.Run("compareWithIp", func(t *testing.T) {
changed := isServiceInstanceChanged(serviceA, serviceC)
assert.Equal(t, true, changed)
})
// compareWithPort
t.Run("compareWithPort", func(t *testing.T) {
changed := isServiceInstanceChanged(serviceA, serviceB)
assert.Equal(t, true, changed)
})
}
func TestHostReactor_isServiceInstanceChangedWithUnOrdered(t *testing.T) {
rand.Seed(time.Now().Unix())
serviceA := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
},
}
serviceB := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
},
}
logger.Info("serviceA:%s and serviceB:%s are comparing", serviceA.Hosts, serviceB.Hosts)
changed := isServiceInstanceChanged(serviceA, serviceB)
assert.True(t, changed)
}
// create random ip addr
func createRandomIp() string {
ip := fmt.Sprintf("%d.%d.%d.%d", rand.Intn(255), rand.Intn(255), rand.Intn(255), rand.Intn(255))
return ip
}
func creatRandomPort() uint64 {
return rand.Uint64()
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 1999-2020 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.
*/
package naming_cache
import (
"sync"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type SubscribeCallback struct {
callbackFuncMap cache.ConcurrentMap
mux *sync.Mutex
}
func NewSubscribeCallback() *SubscribeCallback {
return &SubscribeCallback{callbackFuncMap: cache.NewConcurrentMap(), mux: new(sync.Mutex)}
}
func (ed *SubscribeCallback) IsSubscribed(serviceName, clusters string) bool {
key := util.GetServiceCacheKey(serviceName, clusters)
funcs, ok := ed.callbackFuncMap.Get(key)
if ok {
return len(funcs.([]*SubscribeCallbackFuncWrapper)) > 0
}
return false
}
func (ed *SubscribeCallback) AddCallbackFunc(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
key := util.GetServiceCacheKey(serviceName, clusters)
ed.mux.Lock()
defer ed.mux.Unlock()
var funcSlice []*SubscribeCallbackFuncWrapper
old, ok := ed.callbackFuncMap.Get(key)
if ok {
funcSlice = append(funcSlice, old.([]*SubscribeCallbackFuncWrapper)...)
}
funcSlice = append(funcSlice, callbackWrapper)
ed.callbackFuncMap.Set(key, funcSlice)
}
func (ed *SubscribeCallback) RemoveCallbackFunc(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
logger.Info("removing " + serviceName + " with " + clusters + " to listener map")
key := util.GetServiceCacheKey(serviceName, clusters)
funcs, ok := ed.callbackFuncMap.Get(key)
if ok && funcs != nil {
var newFuncs []*SubscribeCallbackFuncWrapper
for _, funcItem := range funcs.([]*SubscribeCallbackFuncWrapper) {
if funcItem.CallbackFunc != callbackWrapper.CallbackFunc || !funcItem.Selector.Equals(callbackWrapper.Selector) {
newFuncs = append(newFuncs, funcItem)
}
}
ed.callbackFuncMap.Set(key, newFuncs)
}
}
func (ed *SubscribeCallback) ServiceChanged(cacheKey string, service *model.Service) {
funcs, ok := ed.callbackFuncMap.Get(cacheKey)
if ok {
for _, funcItem := range funcs.([]*SubscribeCallbackFuncWrapper) {
funcItem.notifyListener(service)
}
}
}

View File

@ -0,0 +1,230 @@
/*
* Copyright 1999-2020 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.
*/
package naming_cache
import (
"fmt"
"log"
"strings"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
func TestEventDispatcher_AddCallbackFuncs(t *testing.T) {
service := model.Service{
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Println(util.ToJsonString(ed.callbackFuncMap))
},
}
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*SubscribeCallbackFuncWrapper)
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0].CallbackFunc, &param.SubscribeCallback, "callback function must be equal!")
}
}
func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
service := model.Service{
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Printf("func1:%s \n", util.ToJsonString(services))
},
}
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
assert.Equal(t, len(ed.callbackFuncMap.Items()), 1, "callback funcs map length should be 1")
param2 := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Printf("func2:%s \n", util.ToJsonString(services))
},
}
clusterSelector2 := NewClusterSelector(param2.Clusters)
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &param2.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
assert.Equal(t, len(ed.callbackFuncMap.Items()), 1, "callback funcs map length should be 2")
for k, v := range ed.callbackFuncMap.Items() {
log.Printf("key:%s,%d", k, len(v.([]*SubscribeCallbackFuncWrapper)))
}
ed.RemoveCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*SubscribeCallbackFuncWrapper)
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0].CallbackFunc, &param.SubscribeCallback, "callback function must be equal!")
}
}
func TestSubscribeCallback_ServiceChanged(t *testing.T) {
service := model.Service{
Name: "public@@Test",
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("func1:%s \n", util.ToJsonString(services))
},
}
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
param2 := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("func2:%s \n", util.ToJsonString(services))
},
}
clusterSelector2 := NewClusterSelector(param2.Clusters)
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &param2.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
ed.ServiceChanged(cacheKey, &service)
}
func TestSubscribeCallback_RemoveCallbackFunc(t *testing.T) {
ed := NewSubscribeCallback()
serviceName := "Test"
clusters := "default"
groupName := "public"
callback1 := func(services []model.Instance, err error) {
log.Printf("callback1:%s \n", util.ToJsonString(services))
}
clusterSelector1 := NewClusterSelector([]string{clusters})
callbackWrapper1 := NewSubscribeCallbackFuncWrapper(clusterSelector1, &callback1)
callback2 := func(services []model.Instance, err error) {
log.Printf("callback2:%s \n", util.ToJsonString(services))
}
clusterSelector2 := NewClusterSelector([]string{clusters})
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &callback2)
// Add both callbacks
ed.AddCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper1)
ed.AddCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper2)
assert.True(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
// Remove the first callback
ed.RemoveCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper1)
// Check if only the second callback remains
cacheKey := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
funcs, ok := ed.callbackFuncMap.Get(cacheKey)
if !ok || len(funcs.([]*SubscribeCallbackFuncWrapper)) != 1 {
t.Errorf("Expected 1 callback function, got %d", len(funcs.([]*SubscribeCallbackFuncWrapper)))
}
assert.True(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
// Remove the second callback
ed.RemoveCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper2)
// Check if no callbacks remain
funcs, ok = ed.callbackFuncMap.Get(cacheKey)
if ok && len(funcs.([]*SubscribeCallbackFuncWrapper)) != 0 {
t.Errorf("Expected 0 callback functions, got %d", len(funcs.([]*func(services []model.Instance, err error))))
}
assert.False(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
}

View File

@ -0,0 +1,106 @@
package naming_cache
import (
"sort"
"strings"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type Selector interface {
SelectInstance(service *model.Service) []model.Instance
Equals(o Selector) bool
}
type ClusterSelector struct {
ClusterNames string
Clusters []string
}
func NewClusterSelector(clusters []string) *ClusterSelector {
if len(clusters) == 0 {
return &ClusterSelector{
ClusterNames: "",
Clusters: []string{},
}
}
// 创建副本避免外部修改
clustersCopy := make([]string, len(clusters))
copy(clustersCopy, clusters)
return &ClusterSelector{
ClusterNames: joinCluster(clusters),
Clusters: clustersCopy,
}
}
func NewSubscribeCallbackFuncWrapper(selector Selector, callback *func(services []model.Instance, err error)) *SubscribeCallbackFuncWrapper {
if selector == nil {
panic("selector cannot be nil")
}
if callback == nil {
panic("callback cannot be nil")
}
return &SubscribeCallbackFuncWrapper{
Selector: selector,
CallbackFunc: callback,
}
}
type SubscribeCallbackFuncWrapper struct {
Selector Selector
CallbackFunc *func(services []model.Instance, err error)
}
func (ed *SubscribeCallbackFuncWrapper) notifyListener(service *model.Service) {
instances := ed.Selector.SelectInstance(service)
if ed.CallbackFunc != nil {
(*ed.CallbackFunc)(instances, nil)
}
}
func (cs *ClusterSelector) SelectInstance(service *model.Service) []model.Instance {
var instances []model.Instance
if cs.ClusterNames == "" {
return service.Hosts
}
for _, instance := range service.Hosts {
if util.Contains(cs.Clusters, instance.ClusterName) {
instances = append(instances, instance)
}
}
return instances
}
func (cs *ClusterSelector) Equals(o Selector) bool {
if o == nil {
return false
}
if o, ok := o.(*ClusterSelector); ok {
return cs.ClusterNames == o.ClusterNames
}
return false
}
func joinCluster(cluster []string) string {
// 使用map实现去重
uniqueSet := make(map[string]struct{})
for _, item := range cluster {
if item != "" { // 过滤空字符串类似Java中的isNotEmpty
uniqueSet[item] = struct{}{}
}
}
uniqueSlice := make([]string, 0, len(uniqueSet))
for item := range uniqueSet {
uniqueSlice = append(uniqueSlice, item)
}
sort.Strings(uniqueSlice)
// 使用逗号连接
return strings.Join(uniqueSlice, ",")
}

View File

@ -1,67 +1,117 @@
/*
* Copyright 1999-2020 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.
*/
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/pkg/errors"
"context"
"math"
"math/rand"
"os"
"strings"
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// NamingClient ...
type NamingClient struct {
nacos_client.INacosClient
hostReactor HostReactor
serviceProxy NamingProxy
subCallback SubscribeCallback
beatReactor BeatReactor
indexMap cache.ConcurrentMap
ctx context.Context
cancel context.CancelFunc
serviceProxy naming_proxy.INamingProxy
serviceInfoHolder *naming_cache.ServiceInfoHolder
isClosed bool
mutex sync.Mutex
}
func NewNamingClient(nc nacos_client.INacosClient) (NamingClient, error) {
naming := NamingClient{}
clientConfig, err :=
nc.GetClientConfig()
// NewNamingClient ...
func NewNamingClient(nc nacos_client.INacosClient) (*NamingClient, error) {
return NewNamingClientWithRamCredentialProvider(nc, nil)
}
// NewNamingClientWithRamCredentialProvider ...
func NewNamingClientWithRamCredentialProvider(nc nacos_client.INacosClient, provider security.RamCredentialProvider) (*NamingClient, error) {
ctx, cancel := context.WithCancel(context.Background())
rand.Seed(time.Now().UnixNano())
naming := &NamingClient{INacosClient: nc, ctx: ctx, cancel: cancel}
clientConfig, err := nc.GetClientConfig()
if err != nil {
return naming, err
}
serverConfig, err := nc.GetServerConfig()
if err != nil {
return naming, err
}
httpAgent, err := nc.GetHttpAgent()
if err != nil {
return naming, err
}
err = logger.InitLog(clientConfig.LogDir)
if err = initLogger(clientConfig); err != nil {
return naming, err
}
if clientConfig.NamespaceId == "" {
clientConfig.NamespaceId = constant.DEFAULT_NAMESPACE_ID
}
naming.serviceInfoHolder = naming_cache.NewServiceInfoHolder(clientConfig.NamespaceId, clientConfig.CacheDir,
clientConfig.UpdateCacheWhenEmpty, clientConfig.NotLoadCacheAtStart)
naming.serviceProxy, err = NewNamingProxyDelegateWithRamCredentialProvider(ctx, clientConfig, serverConfig, httpAgent, naming.serviceInfoHolder, provider)
if clientConfig.AsyncUpdateService {
go NewServiceInfoUpdater(ctx, naming.serviceInfoHolder, clientConfig.UpdateThreadNum, naming.serviceProxy).asyncUpdateService()
}
if err != nil {
return naming, err
}
naming.subCallback = NewSubscribeCallback()
naming.serviceProxy, err = NewNamingProxy(clientConfig, serverConfig, httpAgent)
if err != nil {
return naming, err
}
naming.hostReactor = NewHostReactor(naming.serviceProxy, clientConfig.CacheDir+string(os.PathSeparator)+"naming",
clientConfig.UpdateThreadNum, clientConfig.NotLoadCacheAtStart, naming.subCallback, clientConfig.UpdateCacheWhenEmpty)
naming.beatReactor = NewBeatReactor(naming.serviceProxy, clientConfig.BeatInterval)
naming.indexMap = cache.NewConcurrentMap()
return naming, nil
}
// 注册服务实例
func initLogger(clientConfig constant.ClientConfig) error {
return logger.InitLogger(logger.BuildLoggerConfig(clientConfig))
}
// RegisterInstance ...
func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool, error) {
if param.GroupName == "" {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
}
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
if param.Metadata == nil {
param.Metadata = make(map[string]string)
}
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
@ -72,75 +122,157 @@ func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
}
beatInfo := model.BeatInfo{
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
}
func (sc *NamingClient) BatchRegisterInstance(param vo.BatchRegisterInstanceParam) (bool, error) {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
}
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
if len(param.Instances) == 0 {
return false, errors.New("instances cannot be empty!")
}
var modelInstances []model.Instance
for _, param := range param.Instances {
if !param.Ephemeral {
return false, errors.Errorf("Batch registration does not allow persistent instance registration! instance:%+v", param)
}
modelInstances = append(modelInstances, model.Instance{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ClusterName: param.ClusterName,
Healthy: param.Healthy,
Enable: param.Enable,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
})
}
return sc.serviceProxy.BatchRegisterInstance(param.ServiceName, param.GroupName, modelInstances)
}
// DeregisterInstance ...
func (sc *NamingClient) DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
ClusterName: param.Cluster,
Ephemeral: param.Ephemeral,
}
return sc.serviceProxy.DeregisterInstance(param.ServiceName, param.GroupName, instance)
}
// UpdateInstance ...
func (sc *NamingClient) UpdateInstance(param vo.UpdateInstanceParam) (bool, error) {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
}
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
if param.Metadata == nil {
param.Metadata = make(map[string]string)
}
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ServiceName: utils.GetGroupName(param.ServiceName, param.GroupName),
Cluster: param.ClusterName,
ClusterName: param.ClusterName,
Healthy: param.Healthy,
Enable: param.Enable,
Weight: param.Weight,
Period: utils.GetDurationWithDefault(param.Metadata, constant.HEART_BEAT_INTERVAL, time.Second*5),
Ephemeral: param.Ephemeral,
}
_, err := sc.serviceProxy.RegisterInstance(utils.GetGroupName(param.ServiceName, param.GroupName), param.GroupName, instance)
if err != nil {
return false, err
}
if instance.Ephemeral {
sc.beatReactor.AddBeatInfo(utils.GetGroupName(param.ServiceName, param.GroupName), beatInfo)
}
return true, nil
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
}
// 注销服务实例
func (sc *NamingClient) DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error) {
if param.GroupName == "" {
// GetService Get service info by Group and DataId, clusters was optional
func (sc *NamingClient) GetService(param vo.GetServiceParam) (service model.Service, err error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
_, err := sc.serviceProxy.DeregisterInstance(utils.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port, param.Cluster, param.Ephemeral)
if err != nil {
return false, err
var ok bool
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
}
sc.beatReactor.RemoveBeatInfo(utils.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port)
return true, nil
service.Clusters = clusters
service.Hosts = clusterSelector.SelectInstance(&service)
return service, err
}
// 获取服务列表
func (sc *NamingClient) GetService(param vo.GetServiceParam) (model.Service, error) {
if param.GroupName == "" {
// GetAllServicesInfo Get all instance by Namespace and Group with page
func (sc *NamingClient) GetAllServicesInfo(param vo.GetAllServiceInfoParam) (model.ServiceList, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
return service, nil
}
func (sc *NamingClient) GetAllServicesInfo(param vo.GetAllServiceInfoParam) ([]model.Service, error) {
if param.GroupName == "" {
param.GroupName = constant.DEFAULT_GROUP
}
if param.NameSpace == "" {
param.NameSpace = constant.DEFAULT_NAMESPACE_ID
}
service := sc.hostReactor.GetAllServiceInfo(param.NameSpace, param.GroupName, strings.Join(param.Clusters, ","))
return service, nil
clientConfig, _ := sc.GetClientConfig()
if len(param.NameSpace) == 0 {
if len(clientConfig.NamespaceId) == 0 {
param.NameSpace = constant.DEFAULT_NAMESPACE_ID
} else {
param.NameSpace = clientConfig.NamespaceId
}
}
services, err := sc.serviceProxy.GetServiceList(param.PageNo, param.PageSize, param.GroupName, param.NameSpace, &model.ExpressionSelector{})
return services, err
}
// SelectAllInstances Get all instance by DataId 和 Group
func (sc *NamingClient) SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error) {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
if service.Hosts == nil || len(service.Hosts) == 0 {
return []model.Instance{}, errors.New("instance list is empty!")
var (
service model.Service
ok bool
err error
)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
}
return service.Hosts, nil
if err != nil {
return []model.Instance{}, err
}
instances := clusterSelector.SelectInstance(&service)
if instances == nil || len(instances) == 0 {
return []model.Instance{}, err
}
return instances, err
}
// SelectInstances Get all instance by DataId, Group and Health
func (sc *NamingClient) SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error) {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
var (
service model.Service
ok bool
err error
)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
if err != nil {
return nil, err
}
}
service.Hosts = clusterSelector.SelectInstance(&service)
return sc.selectInstances(service, param.HealthyOnly)
}
@ -150,6 +282,7 @@ func (sc *NamingClient) selectInstances(service model.Service, healthy bool) ([]
}
hosts := service.Hosts
var result []model.Instance
logger.Infof("select instances with options: [healthy:<%t>], with service:<%s>", healthy, util.GetGroupName(service.Name, service.GroupName))
for _, host := range hosts {
if host.Healthy == healthy && host.Enable && host.Weight > 0 {
result = append(result, host)
@ -158,11 +291,25 @@ func (sc *NamingClient) selectInstances(service model.Service, healthy bool) ([]
return result, nil
}
// SelectOneHealthyInstance Get one healthy instance by DataId and Group
func (sc *NamingClient) SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error) {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
var (
service model.Service
ok bool
err error
)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
if err != nil {
return nil, err
}
}
service.Hosts = clusterSelector.SelectInstance(&service)
return sc.selectOneHealthyInstances(service)
}
@ -186,62 +333,49 @@ func (sc *NamingClient) selectOneHealthyInstances(service model.Service) (*model
return nil, errors.New("healthy instance list is empty!")
}
randomInstances := random(result, mw)
key := utils.GetServiceCacheKey(service.Name, service.Clusters)
i, indexOk := sc.indexMap.Get(key)
var index int
if !indexOk {
index = rand.Intn(len(randomInstances))
} else {
index = i.(int)
index += 1
if index >= len(randomInstances) {
index = index % len(randomInstances)
}
}
sc.indexMap.Set(key, index)
return &randomInstances[index], nil
instance := newChooser(result).pick()
return &instance, nil
}
func random(instances []model.Instance, mw int) []model.Instance {
if len(instances) <= 1 || mw <= 1 {
return instances
}
//实例交叉插入列表,避免列表中是连续的实例
var result = make([]model.Instance, 0)
for i := 1; i <= mw; i++ {
for _, host := range instances {
if int(math.Ceil(host.Weight)) >= i {
result = append(result, host)
}
}
}
return result
}
// 服务监听
// Subscribe ...
func (sc *NamingClient) Subscribe(param *vo.SubscribeParam) error {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
serviceParam := vo.GetServiceParam{
ServiceName: param.ServiceName,
GroupName: param.GroupName,
Clusters: param.Clusters,
}
sc.subCallback.AddCallbackFuncs(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
_, err := sc.GetService(serviceParam)
if err != nil {
return err
}
return nil
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
callbackWrapper := naming_cache.NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
sc.serviceInfoHolder.RegisterCallback(util.GetGroupName(param.ServiceName, param.GroupName), "", callbackWrapper)
_, err := sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
return err
}
//取消服务监听
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) error {
sc.subCallback.RemoveCallbackFuncs(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
return nil
// Unsubscribe ...
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) (err error) {
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
callbackWrapper := naming_cache.NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
serviceFullName := util.GetGroupName(param.ServiceName, param.GroupName)
sc.serviceInfoHolder.DeregisterCallback(serviceFullName, "", callbackWrapper)
if !sc.serviceInfoHolder.IsSubscribed(serviceFullName, "") {
err = sc.serviceProxy.Unsubscribe(param.ServiceName, param.GroupName, "")
}
return err
}
// ServerHealthy ...
func (sc *NamingClient) ServerHealthy() bool {
return sc.serviceProxy.ServerHealthy()
}
// CloseClient ...
func (sc *NamingClient) CloseClient() {
sc.mutex.Lock()
defer sc.mutex.Unlock()
if sc.isClosed {
return
}
sc.serviceProxy.CloseClient()
sc.cancel()
sc.isClosed = true
}

View File

@ -1,39 +1,120 @@
/*
* Copyright 1999-2020 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.
*/
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 09:56
**/
//go:generate mockgen -destination ../../mock/mock_service_client_interface.go -package mock -source=./service_client_interface.go
// INamingClient interface for naming client
type INamingClient interface {
// 注册服务实例
// RegisterInstance use to register instance
// Ip require
// Port require
// Weight require,it must be lager than 0
// Enable require,the instance can be access or not
// Healthy require,the instance is health or not
// Metadata optional
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
RegisterInstance(param vo.RegisterInstanceParam) (bool, error)
// 注销服务实例
// BatchRegisterInstance use to batch register instance
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Instances require,batch register instance list (serviceName, groupName in instances do not need to be set)
BatchRegisterInstance(param vo.BatchRegisterInstanceParam) (bool, error)
// DeregisterInstance use to deregister instance
// Ip required
// Port required
// Tenant optional
// Cluster optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error)
// 获取服务信息
// UpdateInstance use to update instance
// Ip require
// Port require
// Weight require,it must be lager than 0
// Enable require,the instance can be access or not
// Healthy require,the instance is health or not
// Metadata optional
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
UpdateInstance(param vo.UpdateInstanceParam) (bool, error)
// GetService use to get service
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
GetService(param vo.GetServiceParam) (model.Service, error)
//获取所有的实例列表
// SelectAllInstances return all instances,include healthy=false,enable=false,weight<=0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error)
// 获取实例列表
// SelectInstances only return the instances of healthy=${HealthyOnly},enable=true and weight>0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// HealthyOnly optional
SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error)
//获取一个健康的实例
// SelectOneHealthyInstance return one instance by WRR strategy for load balance
// And the instance should be health=true,enable=true and weight>0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error)
// 服务监听
// Subscribe use to subscribe service change event
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// SubscribeCallback require
Subscribe(param *vo.SubscribeParam) error
//取消监听
// Unsubscribe use to unsubscribe service change event
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// SubscribeCallback require
Unsubscribe(param *vo.SubscribeParam) error
//获取全部服务信息
GetAllServicesInfo(param vo.GetAllServiceInfoParam) ([]model.Service, error)
// GetAllServicesInfo use to get all service info by page
GetAllServicesInfo(param vo.GetAllServiceInfoParam) (model.ServiceList, error)
// ServerHealthy use to check the connectivity to server
ServerHealthy() bool
//CloseClient close the GRPC client
CloseClient()
}

View File

@ -1,447 +1,166 @@
/*
* Copyright 1999-2020 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.
*/
package naming_client
import (
"fmt"
"github.com/golang/mock/gomock"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/mock"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
var clientConfigTest = constant.ClientConfig{
TimeoutMs: 20 * 1000,
BeatInterval: 5 * 1000,
ListenInterval: 10 * 1000,
NotLoadCacheAtStart: true,
var clientConfigTest = *constant.NewClientConfig(
constant.WithTimeoutMs(10*1000),
constant.WithBeatInterval(5*1000),
constant.WithNotLoadCacheAtStart(true),
)
var serverConfigTest = *constant.NewServerConfig("127.0.0.1", 80, constant.WithContextPath("/nacos"))
type MockNamingProxy struct {
unsubscribeCalled bool
unsubscribeParams []string // 记录调用参数
}
var serverConfigTest = constant.ServerConfig{
IpAddr: "console.nacos.io",
Port: 80,
ContextPath: "/nacos",
}
var headers = map[string][]string{
"Client-Version": []string{constant.CLIENT_VERSION},
"User-Agent": []string{constant.CLIENT_VERSION},
"Accept-Encoding": []string{"gzip,deflate,sdch"},
"Connection": []string{"Keep-Alive"},
"Request-Module": []string{"Naming"},
"Content-Type": []string{"application/x-www-form-urlencoded"},
func (m *MockNamingProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return true, nil
}
func Test_RegisterServiceInstance_withoutGroupeName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
func (m *MockNamingProxy) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
return true, nil
}
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Eq(map[string]string{
"namespaceId": "public",
"serviceName": "DEFAULT_GROUP@@DEMO",
"groupName": "DEFAULT_GROUP",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "null",
"ephemeral": "true",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
func (m *MockNamingProxy) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return true, nil
}
func (m *MockNamingProxy) GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
return model.ServiceList{Doms: []string{""}}, nil
}
func (m *MockNamingProxy) ServerHealthy() bool {
return true
}
func (m *MockNamingProxy) QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
return &model.Service{}, nil
}
func (m *MockNamingProxy) Subscribe(serviceName, groupName, clusters string) (model.Service, error) {
return model.Service{}, nil
}
func (m *MockNamingProxy) Unsubscribe(serviceName, groupName, clusters string) error {
m.unsubscribeCalled = true
m.unsubscribeParams = []string{serviceName, groupName, clusters}
return nil
}
func (m *MockNamingProxy) CloseClient() {}
func NewTestNamingClient() *NamingClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewNamingClient(&nc)
success, err := client.RegisterInstance(vo.RegisterInstanceParam{
client.serviceProxy = &MockNamingProxy{}
return client
}
func Test_RegisterServiceInstance_withoutGroupName(t *testing.T) {
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
Ephemeral: false,
})
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func Test_RegisterServiceInstance_withGroupeName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Eq(map[string]string{
"namespaceId": "public",
"serviceName": "test_group@@DEMO",
"groupName": "test_group",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "null",
"ephemeral": "true",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
success, err := client.RegisterInstance(vo.RegisterInstanceParam{
func Test_RegisterServiceInstance_withGroupName(t *testing.T) {
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: false,
})
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func Test_RegisterServiceInstance_withCluster(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Eq(map[string]string{
"namespaceId": "public",
"serviceName": "test_group@@DEMO",
"groupName": "test_group",
"clusterName": "test",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "null",
"ephemeral": "true",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
success, err := client.RegisterInstance(vo.RegisterInstanceParam{
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
ClusterName: "test",
Ephemeral: false,
})
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func TestNamingProxy_DeregisterService_WithoutGroupName(t *testing.T) {
success, err := NewTestNamingClient().DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO5",
Ip: "10.0.0.10",
Port: 80,
Ephemeral: true,
})
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func Test_RegisterServiceInstance_401(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Eq(map[string]string{
"namespaceId": "public",
"serviceName": "test_group@@DEMO",
"groupName": "test_group",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "null",
"ephemeral": "true",
})).Times(3).
Return(http_agent.FakeHttpResponse(401, `no auth`), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
result, err := client.RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
func TestNamingProxy_DeregisterService_WithGroupName(t *testing.T) {
success, err := NewTestNamingClient().DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO6",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: true,
})
assert.Equal(t, false, result)
assert.NotNil(t, err)
}
func TestNamingProxy_DeristerService_WithoutGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("DELETE"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Eq(map[string]string{
"namespaceId": "public",
"serviceName": "DEFAULT_GROUP@@DEMO",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"ephemeral": "true",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
client.DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
})
}
func TestNamingProxy_DeristerService_WithGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("DELETE"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Eq(map[string]string{
"namespaceId": "public",
"serviceName": "test_group@@DEMO",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"ephemeral": "true",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
client.DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
})
}
func TestNamingProxy_DeristerService_401(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("DELETE"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Eq(map[string]string{
"namespaceId": "public",
"serviceName": "test_group@@DEMO",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"ephemeral": "true",
})).Times(3).
Return(http_agent.FakeHttpResponse(401, `no auth`), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
client.DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
})
}
var serviceJsonTest = `{
"name": "DEFAULT_GROUP@@DEMO",
"cacheMillis": 1000,
"useSpecifiedURL": false,
"hosts": [{
"valid": true,
"marked": false,
"instanceId": "10.10.10.10-8888-a-DEMO",
"port": 8888,
"ip": "10.10.10.10",
"weight": 1.0,
"metadata": {},
"serviceName":"DEMO",
"enabled":true,
"clusterName":"a"
},{
"valid": true,
"marked": false,
"instanceId": "10.10.10.11-8888-a-DEMO",
"port": 8888,
"ip": "10.10.10.11",
"weight": 1.0,
"metadata": {},
"serviceName":"DEMO",
"enabled":true,
"clusterName":"a"
}
],
"checksum": "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
"lastRefTime": 1528787794594,
"env": "",
"clusters": "a"
}`
var serviceTest = model.Service(model.Service{Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000, UseSpecifiedURL: false,
Hosts: []model.Instance{
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.10-8888-a-DEMO",
Port: 0x22b8,
Ip: "10.10.10.10",
Weight: 1,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO",
Enable: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-8888-a-DEMO",
Port: 0x22b8,
Ip: "10.10.10.11",
Weight: 1,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO",
Enable: true,
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
func TestNamingProxy_GetService_WithoutGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("GET"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance/list"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Any()).Times(2).
Return(http_agent.FakeHttpResponse(200, serviceJsonTest), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
result, err := client.GetService(vo.GetServiceParam{
ServiceName: "DEMO",
Clusters: []string{"a"},
})
assert.Nil(t, err)
assert.Equal(t, serviceTest, result)
}
func TestNamingClient_SelectAllInstancs(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("GET"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance/list"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(20*1000)),
gomock.Any()).Times(2).
Return(http_agent.FakeHttpResponse(200, serviceJsonTest), nil)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instances, err := client.SelectAllInstances(vo.SelectAllInstancesParam{
ServiceName: "DEMO",
Clusters: []string{"a"},
})
fmt.Println(utils.ToJsonString(instances))
assert.Nil(t, err)
assert.Equal(t, 2, len(instances))
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
Weight: 1,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO",
ServiceName: "DEMO1",
Enable: true,
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
@ -453,8 +172,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
@ -466,8 +183,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: false,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
@ -479,8 +194,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
@ -493,65 +206,33 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instance1, err := client.selectOneHealthyInstances(services)
fmt.Println(utils.ToJsonString(instance1))
LastRefTime: 1528787794594, Clusters: "a"}
instance1, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.Nil(t, err)
assert.NotNil(t, instance1)
instance2, err := client.selectOneHealthyInstances(services)
fmt.Println(utils.ToJsonString(instance2))
instance2, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.Nil(t, err)
assert.NotNil(t, instance2)
assert.NotEqual(t, instance1, instance2)
}
func TestNamingClient_SelectOneHealthyInstance_Empty(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instance, err := client.selectOneHealthyInstances(services)
fmt.Println(utils.ToJsonString(instance))
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Clusters: "a"}
instance, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.NotNil(t, err)
assert.Nil(t, instance)
}
func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
@ -563,8 +244,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
@ -576,8 +255,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
@ -589,8 +266,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: false,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
@ -602,8 +277,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
@ -616,34 +289,18 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instances, err := client.selectInstances(services, true)
fmt.Println(utils.ToJsonString(instances))
LastRefTime: 1528787794594, Clusters: "a"}
instances, err := NewTestNamingClient().selectInstances(services, true)
assert.Nil(t, err)
assert.Equal(t, 2, len(instances))
}
func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
@ -655,8 +312,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
@ -668,8 +323,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
@ -681,8 +334,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: false,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
@ -694,8 +345,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
@ -708,47 +357,207 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instances, err := client.selectInstances(services, false)
fmt.Println(utils.ToJsonString(instances))
LastRefTime: 1528787794594, Clusters: "a"}
instances, err := NewTestNamingClient().selectInstances(services, false)
assert.Nil(t, err)
assert.Equal(t, 1, len(instances))
}
func TestNamingClient_SelectInstances_Empty(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instances, err := client.selectInstances(services, false)
fmt.Println(utils.ToJsonString(instances))
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Clusters: "a"}
instances, err := NewTestNamingClient().selectInstances(services, false)
assert.NotNil(t, err)
assert.Equal(t, 0, len(instances))
}
func TestNamingClient_GetAllServicesInfo(t *testing.T) {
result, err := NewTestNamingClient().GetAllServicesInfo(vo.GetAllServiceInfoParam{
GroupName: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 20,
})
assert.NotNil(t, result.Doms)
assert.Nil(t, err)
}
func BenchmarkNamingClient_SelectOneHealthyInstances(b *testing.B) {
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
Weight: 10,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO1",
Enable: true,
Healthy: true,
},
{
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
Weight: 10,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO2",
Enable: true,
Healthy: true,
},
{
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
Weight: 1,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO3",
Enable: true,
Healthy: false,
},
{
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
Weight: 1,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO4",
Enable: false,
Healthy: true,
},
{
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
Weight: 0,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO5",
Enable: true,
Healthy: true,
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Clusters: "a"}
client := NewTestNamingClient()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = client.selectOneHealthyInstances(services)
}
}
func TestNamingClient_Unsubscribe_WithCallback_ShouldNotCallServiceProxyUnsubscribe(t *testing.T) {
// 创建一个带有回调函数的订阅参数
callback := func(services []model.Instance, err error) {
// 空回调函数
}
param := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
SubscribeCallback: callback,
}
// 创建测试客户端
client := NewTestNamingClient()
mockProxy := client.serviceProxy.(*MockNamingProxy)
// 执行 Unsubscribe
err := client.Unsubscribe(param)
// 验证没有错误
assert.Nil(t, err)
assert.True(t, mockProxy.unsubscribeCalled)
}
func TestNamingClient_Unsubscribe_WithoutCallback_ShouldCallServiceProxyUnsubscribe(t *testing.T) {
// 创建一个没有回调函数的订阅参数
param := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
// SubscribeCallback 为 nil
}
// 创建测试客户端
client := NewTestNamingClient()
// 获取原始的 MockNamingProxy 来检查调用状态
mockProxy := client.serviceProxy.(*MockNamingProxy)
// 执行 Unsubscribe
err := client.Unsubscribe(param)
// 验证没有错误
assert.Nil(t, err)
assert.True(t, mockProxy.unsubscribeCalled)
}
// TestNamingClient_Unsubscribe_Integration_Test 集成测试,使用真实的 ServiceInfoHolder 来测试修复后的逻辑
func TestNamingClient_Unsubscribe_Integration_Test(t *testing.T) {
// 创建测试客户端
client := NewTestNamingClient()
// 获取原始的 MockNamingProxy 来检查调用状态
mockProxy := client.serviceProxy.(*MockNamingProxy)
// 创建回调函数
callback1 := func(services []model.Instance, err error) {
// 回调函数1
}
callback2 := func(services []model.Instance, err error) {
// 回调函数2
}
// 测试场景1先注册两个回调函数然后取消订阅第一个
// 这种情况下,取消订阅第一个回调函数后,还有其他回调函数,所以不应该调用 serviceProxy.Unsubscribe
// 注册第一个回调函数
param1 := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
SubscribeCallback: callback1,
}
// 注册第二个回调函数
param2 := &vo.SubscribeParam{
ServiceName: "test-service",
GroupName: "test-group",
Clusters: []string{"test-cluster"},
SubscribeCallback: callback2,
}
// 先注册两个回调函数
err := client.Subscribe(param1)
assert.Nil(t, err)
err = client.Subscribe(param2)
assert.Nil(t, err)
// 重置 MockNamingProxy 的调用状态
mockProxy.unsubscribeCalled = false
mockProxy.unsubscribeParams = nil
// 取消订阅第一个回调函数
err = client.Unsubscribe(param1)
assert.Nil(t, err)
assert.False(t, mockProxy.unsubscribeCalled)
// 取消订阅第二个回调函数
err = client.Unsubscribe(param2)
assert.Nil(t, err)
assert.True(t, mockProxy.unsubscribeCalled)
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 1999-2020 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.
*/
package naming_grpc
import (
"strings"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type ConnectionEventListener struct {
clientProxy naming_proxy.INamingProxy
registeredInstanceCached cache.ConcurrentMap
subscribes cache.ConcurrentMap
}
func NewConnectionEventListener(clientProxy naming_proxy.INamingProxy) *ConnectionEventListener {
return &ConnectionEventListener{
clientProxy: clientProxy,
registeredInstanceCached: cache.NewConcurrentMap(),
subscribes: cache.NewConcurrentMap(),
}
}
func (c *ConnectionEventListener) OnConnected() {
c.redoSubscribe()
c.redoRegisterEachService()
}
func (c *ConnectionEventListener) OnDisConnect() {
}
func (c *ConnectionEventListener) redoSubscribe() {
grpcProxy, ok := c.clientProxy.(*NamingGrpcProxy)
if !ok {
logger.Error("redo subscribe clientProxy type error")
return
}
for _, key := range c.subscribes.Keys() {
info := strings.Split(key, constant.SERVICE_INFO_SPLITER)
var err error
var service model.Service
if len(info) > 2 {
service, err = c.clientProxy.Subscribe(info[1], info[0], info[2])
} else {
service, err = c.clientProxy.Subscribe(info[1], info[0], "")
}
if err != nil {
logger.Warnf("redo subscribe service:%s faild:%+v", info[1], err)
continue
}
grpcProxy.serviceInfoHolder.ProcessService(&service)
}
}
func (c *ConnectionEventListener) redoRegisterEachService() {
for k, v := range c.registeredInstanceCached.Items() {
info := strings.Split(k, constant.SERVICE_INFO_SPLITER)
serviceName := info[1]
groupName := info[0]
if instance, ok := v.(model.Instance); ok {
if _, err := c.clientProxy.RegisterInstance(serviceName, groupName, instance); err != nil {
logger.Warnf("redo register service:%s groupName:%s faild:%s", info[1], info[0], err.Error())
continue
}
}
if instances, ok := v.([]model.Instance); ok {
if _, err := c.clientProxy.BatchRegisterInstance(serviceName, groupName, instances); err != nil {
logger.Warnf("redo batch register service:%s groupName:%s faild:%s", info[1], info[0], err.Error())
continue
}
}
}
}
func (c *ConnectionEventListener) CacheInstanceForRedo(serviceName, groupName string, instance model.Instance) {
key := util.GetGroupName(serviceName, groupName)
c.registeredInstanceCached.Set(key, instance)
}
func (c *ConnectionEventListener) CacheInstancesForRedo(serviceName, groupName string, instances []model.Instance) {
key := util.GetGroupName(serviceName, groupName)
c.registeredInstanceCached.Set(key, instances)
}
func (c *ConnectionEventListener) RemoveInstanceForRedo(serviceName, groupName string, instance model.Instance) {
key := util.GetGroupName(serviceName, groupName)
_, ok := c.registeredInstanceCached.Get(key)
if !ok {
return
}
c.registeredInstanceCached.Remove(key)
}
func (c *ConnectionEventListener) CacheSubscriberForRedo(fullServiceName, clusters string) {
key := util.GetServiceCacheKey(fullServiceName, clusters)
if !c.IsSubscriberCached(key) {
c.subscribes.Set(key, struct{}{})
}
}
func (c *ConnectionEventListener) IsSubscriberCached(key string) bool {
_, ok := c.subscribes.Get(key)
return ok
}
func (c *ConnectionEventListener) RemoveSubscriberForRedo(fullServiceName, clusters string) {
c.subscribes.Remove(util.GetServiceCacheKey(fullServiceName, clusters))
}

View File

@ -0,0 +1,34 @@
package naming_grpc
import (
"github.com/golang/mock/gomock"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"testing"
)
func TestRedoSubscribe(t *testing.T) {
t.Skip("Skipping test,It failed due to a previous commit and is difficult to modify because of the use of struct type assertions in the code.")
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockProxy := naming_proxy.NewMockINamingProxy(ctrl)
evListener := NewConnectionEventListener(mockProxy)
cases := []struct {
serviceName string
groupName string
clusters string
}{
{"service-a", "group-a", ""},
{"service-b", "group-b", "cluster-b"},
}
for _, v := range cases {
fullServiceName := util.GetGroupName(v.serviceName, v.groupName)
evListener.CacheSubscriberForRedo(fullServiceName, v.clusters)
mockProxy.EXPECT().Subscribe(v.serviceName, v.groupName, v.clusters)
evListener.redoSubscribe()
evListener.RemoveSubscriberForRedo(fullServiceName, v.clusters)
}
}

View File

@ -0,0 +1,204 @@
/*
* Copyright 1999-2020 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.
*/
package naming_grpc
import (
"context"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
// NamingGrpcProxy ...
type NamingGrpcProxy struct {
clientConfig constant.ClientConfig
nacosServer *nacos_server.NacosServer
rpcClient rpc.IRpcClient
eventListener *ConnectionEventListener
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
// NewNamingGrpcProxy create naming grpc proxy
func NewNamingGrpcProxy(ctx context.Context, clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer,
serviceInfoHolder *naming_cache.ServiceInfoHolder) (*NamingGrpcProxy, error) {
srvProxy := NamingGrpcProxy{
clientConfig: clientCfg,
nacosServer: nacosServer,
serviceInfoHolder: serviceInfoHolder,
}
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
labels := map[string]string{
constant.LABEL_SOURCE: constant.LABEL_SOURCE_SDK,
constant.LABEL_MODULE: constant.LABEL_MODULE_NAMING,
}
iRpcClient, err := rpc.CreateClient(ctx, uid.String(), rpc.GRPC, labels, srvProxy.nacosServer, &clientCfg.TLSCfg, clientCfg.AppConnLabels)
if err != nil {
return nil, err
}
srvProxy.rpcClient = iRpcClient
rpcClient := srvProxy.rpcClient.GetRpcClient()
rpcClient.Start()
rpcClient.RegisterServerRequestHandler(func() rpc_request.IRequest {
return &rpc_request.NotifySubscriberRequest{NamingRequest: &rpc_request.NamingRequest{}}
}, &rpc.NamingPushRequestHandler{ServiceInfoHolder: serviceInfoHolder})
srvProxy.eventListener = NewConnectionEventListener(&srvProxy)
rpcClient.RegisterConnectionListener(srvProxy.eventListener)
return &srvProxy, nil
}
func (proxy *NamingGrpcProxy) requestToServer(request rpc_request.IRequest) (rpc_response.IResponse, error) {
start := time.Now()
proxy.nacosServer.InjectSecurityInfo(request.GetHeaders(), security.BuildNamingResourceByRequest(request))
response, err := proxy.rpcClient.GetRpcClient().Request(request, int64(proxy.clientConfig.TimeoutMs))
monitor.GetNamingRequestMonitor(constant.GRPC, request.GetRequestType(), rpc_response.GetGrpcResponseStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
return response, err
}
// RegisterInstance ...
func (proxy *NamingGrpcProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
logger.Infof("register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instance))
proxy.eventListener.CacheInstanceForRedo(serviceName, groupName, instance)
instanceRequest := rpc_request.NewInstanceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, "registerInstance", instance)
response, err := proxy.requestToServer(instanceRequest)
if err != nil {
return false, err
}
return response.IsSuccess(), err
}
// BatchRegisterInstance ...
func (proxy *NamingGrpcProxy) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
logger.Infof("batch register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instances))
proxy.eventListener.CacheInstancesForRedo(serviceName, groupName, instances)
batchInstanceRequest := rpc_request.NewBatchInstanceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, "batchRegisterInstance", instances)
response, err := proxy.requestToServer(batchInstanceRequest)
if err != nil {
return false, err
}
return response.IsSuccess(), err
}
// DeregisterInstance ...
func (proxy *NamingGrpcProxy) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
logger.Infof("deregister instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
proxy.clientConfig.NamespaceId, serviceName, instance.Ip, instance.Port, instance.ClusterName)
instanceRequest := rpc_request.NewInstanceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, "deregisterInstance", instance)
response, err := proxy.requestToServer(instanceRequest)
proxy.eventListener.RemoveInstanceForRedo(serviceName, groupName, instance)
if err != nil {
return false, err
}
return response.IsSuccess(), err
}
// GetServiceList ...
func (proxy *NamingGrpcProxy) GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
var selectorStr string
if selector != nil {
switch selector.Type {
case "label":
selectorStr = util.ToJsonString(selector)
default:
break
}
}
response, err := proxy.requestToServer(rpc_request.NewServiceListRequest(namespaceId, "",
groupName, int(pageNo), int(pageSize), selectorStr))
if err != nil {
return model.ServiceList{}, err
}
serviceListResponse := response.(*rpc_response.ServiceListResponse)
return model.ServiceList{
Count: int64(serviceListResponse.Count),
Doms: serviceListResponse.ServiceNames,
}, nil
}
// ServerHealthy ...
func (proxy *NamingGrpcProxy) ServerHealthy() bool {
return proxy.rpcClient.GetRpcClient().IsRunning()
}
// QueryInstancesOfService ...
func (proxy *NamingGrpcProxy) QueryInstancesOfService(serviceName, groupName, cluster string, udpPort int, healthyOnly bool) (*model.Service, error) {
response, err := proxy.requestToServer(rpc_request.NewServiceQueryRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, cluster,
healthyOnly, udpPort))
if err != nil {
return nil, err
}
queryServiceResponse := response.(*rpc_response.QueryServiceResponse)
return &queryServiceResponse.ServiceInfo, nil
}
func (proxy *NamingGrpcProxy) IsSubscribed(serviceName, groupName string, clusters string) bool {
return proxy.eventListener.IsSubscriberCached(util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters))
}
// Subscribe ...
func (proxy *NamingGrpcProxy) Subscribe(serviceName, groupName string, clusters string) (model.Service, error) {
logger.Infof("Subscribe Service namespaceId:<%s>, serviceName:<%s>, groupName:<%s>, clusters:<%s>",
proxy.clientConfig.NamespaceId, serviceName, groupName, clusters)
proxy.eventListener.CacheSubscriberForRedo(util.GetGroupName(serviceName, groupName), clusters)
request := rpc_request.NewSubscribeServiceRequest(proxy.clientConfig.NamespaceId, serviceName,
groupName, clusters, true)
request.Headers["app"] = proxy.clientConfig.AppName
response, err := proxy.requestToServer(request)
if err != nil {
return model.Service{}, err
}
subscribeServiceResponse := response.(*rpc_response.SubscribeServiceResponse)
return subscribeServiceResponse.ServiceInfo, nil
}
// Unsubscribe ...
func (proxy *NamingGrpcProxy) Unsubscribe(serviceName, groupName, clusters string) error {
logger.Infof("Unsubscribe Service namespaceId:<%s>, serviceName:<%s>, groupName:<%s>, clusters:<%s>",
proxy.clientConfig.NamespaceId, serviceName, groupName, clusters)
proxy.eventListener.RemoveSubscriberForRedo(util.GetGroupName(serviceName, groupName), clusters)
_, err := proxy.requestToServer(rpc_request.NewSubscribeServiceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName,
clusters, false))
return err
}
func (proxy *NamingGrpcProxy) CloseClient() {
logger.Info("Close Nacos Go SDK Client...")
proxy.rpcClient.GetRpcClient().Shutdown()
}

View File

@ -0,0 +1,40 @@
package naming_grpc
import "github.com/nacos-group/nacos-sdk-go/v2/model"
type MockNamingGrpc struct {
}
func (m *MockNamingGrpc) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return true, nil
}
func (m *MockNamingGrpc) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
return true, nil
}
func (m *MockNamingGrpc) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return true, nil
}
func (m *MockNamingGrpc) GetServiceList(pageNo uint32, pageSize uint32, groupName string, selector *model.ExpressionSelector) (model.ServiceList, error) {
return model.ServiceList{Doms: []string{""}}, nil
}
func (m *MockNamingGrpc) ServerHealthy() bool {
return true
}
func (m *MockNamingGrpc) QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
return &model.Service{}, nil
}
func (m *MockNamingGrpc) Subscribe(serviceName, groupName, clusters string) (model.Service, error) {
return model.Service{}, nil
}
func (m *MockNamingGrpc) Unsubscribe(serviceName, groupName, clusters string) error {
return nil
}
func (m *MockNamingGrpc) CloseClient() {}

View File

@ -0,0 +1,160 @@
/*
* Copyright 1999-2020 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.
*/
package naming_http
import (
"context"
"fmt"
"net/http"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/buger/jsonparser"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"golang.org/x/sync/semaphore"
)
type BeatReactor struct {
ctx context.Context
beatMap cache.ConcurrentMap
nacosServer *nacos_server.NacosServer
beatThreadCount int
beatThreadSemaphore *semaphore.Weighted
beatRecordMap cache.ConcurrentMap
clientCfg constant.ClientConfig
mux *sync.Mutex
}
const DefaultBeatThreadNum = 20
func NewBeatReactor(ctx context.Context, clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer) BeatReactor {
br := BeatReactor{}
br.ctx = ctx
br.beatMap = cache.NewConcurrentMap()
br.nacosServer = nacosServer
br.clientCfg = clientCfg
br.beatThreadCount = DefaultBeatThreadNum
br.beatRecordMap = cache.NewConcurrentMap()
br.beatThreadSemaphore = semaphore.NewWeighted(int64(br.beatThreadCount))
br.mux = new(sync.Mutex)
return br
}
func buildKey(serviceName string, ip string, port uint64) string {
return serviceName + constant.NAMING_INSTANCE_ID_SPLITTER + ip + constant.NAMING_INSTANCE_ID_SPLITTER + strconv.Itoa(int(port))
}
func (br *BeatReactor) AddBeatInfo(serviceName string, beatInfo *model.BeatInfo) {
logger.Infof("adding beat: <%s> to beat map", util.ToJsonString(beatInfo))
k := buildKey(serviceName, beatInfo.Ip, beatInfo.Port)
defer br.mux.Unlock()
br.mux.Lock()
if data, ok := br.beatMap.Get(k); ok {
beatInfo = data.(*model.BeatInfo)
atomic.StoreInt32(&beatInfo.State, int32(model.StateShutdown))
br.beatMap.Remove(k)
}
br.beatMap.Set(k, beatInfo)
beatInfo.Metadata = util.DeepCopyMap(beatInfo.Metadata)
monitor.GetDom2BeatSizeMonitor().Set(float64(br.beatMap.Count()))
go br.sendInstanceBeat(k, beatInfo)
}
func (br *BeatReactor) RemoveBeatInfo(serviceName string, ip string, port uint64) {
logger.Infof("remove beat: %s@%s:%d from beat map", serviceName, ip, port)
k := buildKey(serviceName, ip, port)
defer br.mux.Unlock()
br.mux.Lock()
data, exist := br.beatMap.Get(k)
if exist {
beatInfo := data.(*model.BeatInfo)
atomic.StoreInt32(&beatInfo.State, int32(model.StateShutdown))
}
monitor.GetDom2BeatSizeMonitor().Set(float64(br.beatMap.Count()))
br.beatMap.Remove(k)
}
func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
t := time.NewTimer(beatInfo.Period)
defer t.Stop()
for {
br.beatThreadSemaphore.Acquire(br.ctx, 1)
//如果当前实例注销,则进行停止心跳
if atomic.LoadInt32(&beatInfo.State) == int32(model.StateShutdown) {
logger.Infof("instance[%s] stop heartBeating", k)
br.beatThreadSemaphore.Release(1)
return
}
//进行心跳通信
beatInterval, err := br.SendBeat(beatInfo)
if err != nil {
logger.Errorf("beat to server return error:%+v", err)
br.beatThreadSemaphore.Release(1)
t := time.NewTimer(beatInfo.Period)
<-t.C
continue
}
if beatInterval > 0 {
beatInfo.Period = time.Duration(time.Millisecond.Nanoseconds() * beatInterval)
}
br.beatRecordMap.Set(k, util.CurrentMillis())
br.beatThreadSemaphore.Release(1)
t.Reset(beatInfo.Period)
select {
case <-t.C:
case <-br.ctx.Done():
return
}
}
}
func (br *BeatReactor) SendBeat(info *model.BeatInfo) (int64, error) {
logger.Infof("namespaceId:<%s> sending beat to server:<%s>",
br.clientCfg.NamespaceId, util.ToJsonString(info))
params := map[string]string{}
params["namespaceId"] = br.clientCfg.NamespaceId
params["serviceName"] = info.ServiceName
params["beat"] = util.ToJsonString(info)
api := constant.SERVICE_BASE_PATH + "/instance/beat"
result, err := br.nacosServer.ReqApi(api, params, http.MethodPut, br.clientCfg)
if err != nil {
return 0, err
}
if result != "" {
interVal, err := jsonparser.GetInt([]byte(result), "clientBeatInterval")
if err != nil {
return 0, errors.New(fmt.Sprintf("namespaceId:<%s> sending beat to server:<%s> get 'clientBeatInterval' from <%s> error:<%+v>", br.clientCfg.NamespaceId, util.ToJsonString(info), result, err))
} else {
return interVal, nil
}
}
return 0, nil
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 1999-2020 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.
*/
package naming_http
import (
"context"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/stretchr/testify/assert"
)
func TestBeatReactor_AddBeatInfo(t *testing.T) {
br := NewBeatReactor(context.Background(), constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo := &model.BeatInfo{
Ip: "127.0.0.1",
Port: 8080,
Metadata: map[string]string{},
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo)
key := buildKey(util.GetGroupName(serviceName, groupName), beatInfo.Ip, beatInfo.Port)
result, ok := br.beatMap.Get(key)
assert.Equal(t, ok, true, "key should exists!")
assert.ObjectsAreEqual(result.(*model.BeatInfo), beatInfo)
}
func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
br := NewBeatReactor(context.Background(), constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo1 := &model.BeatInfo{
Ip: "127.0.0.1",
Port: 8080,
Metadata: map[string]string{},
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo1)
beatInfo2 := &model.BeatInfo{
Ip: "127.0.0.2",
Port: 8080,
Metadata: map[string]string{},
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo2)
br.RemoveBeatInfo(util.GetGroupName(serviceName, groupName), "127.0.0.1", 8080)
key := buildKey(util.GetGroupName(serviceName, groupName), beatInfo2.Ip, beatInfo2.Port)
result, ok := br.beatMap.Get(key)
assert.Equal(t, br.beatMap.Count(), 1, "beatinfo map length should be 1")
assert.Equal(t, ok, true, "key should exists!")
assert.ObjectsAreEqual(result.(*model.BeatInfo), beatInfo2)
}

View File

@ -0,0 +1,218 @@
/*
* Copyright 1999-2020 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.
*/
package naming_http
import (
"context"
"net/http"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/buger/jsonparser"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
// NamingHttpProxy ...
type NamingHttpProxy struct {
clientConfig constant.ClientConfig
nacosServer *nacos_server.NacosServer
beatReactor BeatReactor
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
// NewNamingHttpProxy create naming http proxy
func NewNamingHttpProxy(ctx context.Context, clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer,
serviceInfoHolder *naming_cache.ServiceInfoHolder) (*NamingHttpProxy, error) {
srvProxy := NamingHttpProxy{
clientConfig: clientCfg,
nacosServer: nacosServer,
serviceInfoHolder: serviceInfoHolder,
}
srvProxy.beatReactor = NewBeatReactor(ctx, clientCfg, nacosServer)
NewPushReceiver(ctx, serviceInfoHolder).startServer()
return &srvProxy, nil
}
// RegisterInstance ...
func (proxy *NamingHttpProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
logger.Infof("register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instance))
serviceName = util.GetGroupName(serviceName, groupName)
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["groupName"] = groupName
params["app"] = proxy.clientConfig.AppName
params["clusterName"] = instance.ClusterName
params["ip"] = instance.Ip
params["port"] = strconv.Itoa(int(instance.Port))
params["weight"] = strconv.FormatFloat(instance.Weight, 'f', -1, 64)
params["enable"] = strconv.FormatBool(instance.Enable)
params["healthy"] = strconv.FormatBool(instance.Healthy)
params["metadata"] = util.ToJsonString(instance.Metadata)
params["ephemeral"] = strconv.FormatBool(instance.Ephemeral)
_, err := proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodPost, proxy.clientConfig)
if err != nil {
return false, err
}
if instance.Ephemeral {
beatInfo := &model.BeatInfo{
Ip: instance.Ip,
Port: instance.Port,
Metadata: instance.Metadata,
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: instance.ClusterName,
Weight: instance.Weight,
Period: util.GetDurationWithDefault(instance.Metadata, constant.HEART_BEAT_INTERVAL, time.Second*5),
State: model.StateRunning,
}
proxy.beatReactor.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo)
}
return true, nil
}
func (proxy *NamingHttpProxy) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
panic("implement me")
}
// DeregisterInstance ...
func (proxy *NamingHttpProxy) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
serviceName = util.GetGroupName(serviceName, groupName)
logger.Infof("deregister instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
proxy.clientConfig.NamespaceId, serviceName, instance.Ip, instance.Port, instance.ClusterName)
proxy.beatReactor.RemoveBeatInfo(serviceName, instance.Ip, instance.Port)
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["clusterName"] = instance.ClusterName
params["ip"] = instance.Ip
params["port"] = strconv.Itoa(int(instance.Port))
params["ephemeral"] = strconv.FormatBool(instance.Ephemeral)
_, err := proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodDelete, proxy.clientConfig)
if err != nil {
return false, err
}
return true, nil
}
// GetServiceList ...
func (proxy *NamingHttpProxy) GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
params := map[string]string{}
params["namespaceId"] = namespaceId
params["groupName"] = groupName
params["pageNo"] = strconv.Itoa(int(pageNo))
params["pageSize"] = strconv.Itoa(int(pageSize))
if selector != nil {
switch selector.Type {
case "label":
params["selector"] = util.ToJsonString(selector)
break
default:
break
}
}
serviceList := model.ServiceList{}
api := constant.SERVICE_BASE_PATH + "/service/list"
result, err := proxy.nacosServer.ReqApi(api, params, http.MethodGet, proxy.clientConfig)
if err != nil {
return serviceList, err
}
if result == "" {
return serviceList, errors.New("request server return empty")
}
count, err := jsonparser.GetInt([]byte(result), "count")
if err != nil {
return serviceList, errors.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'count' from <%s> error:<%+v>", namespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err)
}
var doms []string
_, err = jsonparser.ArrayEach([]byte(result), func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
doms = append(doms, string(value))
}, "doms")
if err != nil {
return serviceList, errors.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'doms' from <%s> error:<%+v> ", namespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err)
}
serviceList.Count = count
serviceList.Doms = doms
return serviceList, nil
}
// ServerHealthy ...
func (proxy *NamingHttpProxy) ServerHealthy() bool {
api := constant.SERVICE_BASE_PATH + "/operator/metrics"
result, err := proxy.nacosServer.ReqApi(api, map[string]string{}, http.MethodGet, proxy.clientConfig)
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
return false
}
if result != "" {
status, err := jsonparser.GetString([]byte(result), "status")
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
} else {
return status == "UP"
}
}
return false
}
// QueryInstancesOfService ...
func (proxy *NamingHttpProxy) QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
param := make(map[string]string)
param["namespaceId"] = proxy.clientConfig.NamespaceId
param["serviceName"] = util.GetGroupName(serviceName, groupName)
param["app"] = proxy.clientConfig.AppName
param["clusters"] = clusters
param["udpPort"] = strconv.Itoa(udpPort)
param["healthyOnly"] = strconv.FormatBool(healthyOnly)
param["clientIP"] = util.LocalIP()
api := constant.SERVICE_PATH + "/list"
result, err := proxy.nacosServer.ReqApi(api, param, http.MethodGet, proxy.clientConfig)
if err != nil {
return nil, err
}
return util.JsonToService(result), nil
}
// Subscribe ...
func (proxy *NamingHttpProxy) Subscribe(serviceName, groupName, clusters string) (model.Service, error) {
return model.Service{}, nil
}
// Unsubscribe ...
func (proxy *NamingHttpProxy) Unsubscribe(serviceName, groupName, clusters string) error {
return nil
}
func (proxy *NamingHttpProxy) CloseClient() {
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 1999-2020 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.
*/
package naming_http
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"io"
"math/rand"
"net"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type PushReceiver struct {
ctx context.Context
port int
host string
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
type PushData struct {
PushType string `json:"type"`
Data string `json:"data"`
LastRefTime int64 `json:"lastRefTime"`
}
var (
GZIP_MAGIC = []byte("\x1F\x8B")
)
func NewPushReceiver(ctx context.Context, serviceInfoHolder *naming_cache.ServiceInfoHolder) *PushReceiver {
pr := PushReceiver{
ctx: ctx,
serviceInfoHolder: serviceInfoHolder,
}
return &pr
}
func (us *PushReceiver) tryListen() (*net.UDPConn, bool) {
addr, err := net.ResolveUDPAddr("udp", us.host+":"+strconv.Itoa(us.port))
if err != nil {
logger.Errorf("can't resolve address,err: %+v", err)
return nil, false
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
logger.Errorf("error listening %s:%d,err:%+v", us.host, us.port, err)
return nil, false
}
return conn, true
}
func (us *PushReceiver) startServer() {
var (
conn *net.UDPConn
ok bool
)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 3; i++ {
port := r.Intn(1000) + 54951
us.port = port
conn, ok = us.tryListen()
if ok {
logger.Infof("udp server start, port: " + strconv.Itoa(port))
break
}
if !ok && i == 2 {
logger.Errorf("failed to start udp server after trying 3 times.")
}
}
if conn == nil {
return
}
go func() {
defer conn.Close()
for {
select {
case <-us.ctx.Done():
return
default:
us.handleClient(conn)
}
}
}()
}
func (us *PushReceiver) handleClient(conn *net.UDPConn) {
data := make([]byte, 4024)
n, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
logger.Errorf("failed to read UDP msg because of %+v", err)
return
}
s := TryDecompressData(data[:n])
logger.Info("receive push: "+s+" from: ", remoteAddr)
var pushData PushData
err1 := json.Unmarshal([]byte(s), &pushData)
if err1 != nil {
logger.Infof("failed to process push data.err:%+v", err1)
return
}
ack := make(map[string]string)
if pushData.PushType == "dom" || pushData.PushType == "service" {
us.serviceInfoHolder.ProcessServiceJson(pushData.Data)
ack["type"] = "push-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = ""
} else if pushData.PushType == "dump" {
ack["type"] = "dump-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = util.ToJsonString(us.serviceInfoHolder.ServiceInfoMap)
} else {
ack["type"] = "unknow-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = ""
}
bs, _ := json.Marshal(ack)
c, err := conn.WriteToUDP(bs, remoteAddr)
if err != nil {
logger.Errorf("WriteToUDP failed,return:%d,err:%+v", c, err)
}
}
func TryDecompressData(data []byte) string {
if !IsGzipFile(data) {
return string(data)
}
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
logger.Errorf("failed to decompress gzip data,err:%+v", err)
return ""
}
defer reader.Close()
bs, err := io.ReadAll(reader)
if err != nil {
logger.Errorf("failed to decompress gzip data,err:%+v", err)
return ""
}
return string(bs)
}
func IsGzipFile(data []byte) bool {
if len(data) < 2 {
return false
}
return bytes.HasPrefix(data, GZIP_MAGIC)
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 1999-2020 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.
*/
package naming_client
import (
"math/rand"
"sort"
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
type Chooser struct {
data []model.Instance
totals []int
max int
}
type instance []model.Instance
func (a instance) Len() int {
return len(a)
}
func (a instance) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a instance) Less(i, j int) bool {
return a[i].Weight < a[j].Weight
}
// NewChooser initializes a new Chooser for picking from the provided Choices.
func newChooser(instances []model.Instance) Chooser {
sort.Sort(instance(instances))
totals := make([]int, len(instances))
runningTotal := 0
for i, c := range instances {
runningTotal += int(c.Weight)
totals[i] = runningTotal
}
return Chooser{data: instances, totals: totals, max: runningTotal}
}
func (chs Chooser) pick() model.Instance {
r := rand.Intn(chs.max) + 1
i := sort.SearchInts(chs.totals, r)
return chs.data[i]
}

View File

@ -1,166 +0,0 @@
package naming_client
import (
"errors"
"fmt"
"github.com/buger/jsonparser"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"log"
"net/http"
"strconv"
)
type NamingProxy struct {
clientConfig constant.ClientConfig
nacosServer nacos_server.NacosServer
}
func NewNamingProxy(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig, httpAgent http_agent.IHttpAgent) (NamingProxy, error) {
srvProxy := NamingProxy{}
srvProxy.clientConfig = clientCfg
var err error
srvProxy.nacosServer, err = nacos_server.NewNacosServer(serverCfgs, httpAgent, clientCfg.TimeoutMs, clientCfg.Endpoint)
if err != nil {
return srvProxy, err
}
return srvProxy, nil
}
func (proxy *NamingProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (string, error) {
log.Printf("[INFO] register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s> \n", proxy.clientConfig.NamespaceId, serviceName, utils.ToJsonString(instance))
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["groupName"] = groupName
params["clusterName"] = instance.ClusterName
params["ip"] = instance.Ip
params["port"] = strconv.Itoa(int(instance.Port))
params["weight"] = strconv.FormatFloat(instance.Weight, 'f', -1, 64)
params["enable"] = strconv.FormatBool(instance.Enable)
params["healthy"] = strconv.FormatBool(instance.Healthy)
params["metadata"] = utils.ToJsonString(instance.Metadata)
params["ephemeral"] = strconv.FormatBool(instance.Ephemeral)
return proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodPost)
}
func (proxy *NamingProxy) DeregisterInstance(serviceName string, ip string, port uint64, clusterName string, ephemeral bool) (string, error) {
log.Printf("[INFO] deregister instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s> \n", proxy.clientConfig.NamespaceId, serviceName, ip, port, clusterName)
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["clusterName"] = clusterName
params["ip"] = ip
params["port"] = strconv.Itoa(int(port))
params["ephemeral"] = strconv.FormatBool(ephemeral)
return proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodDelete)
}
func (proxy *NamingProxy) SendBeat(info model.BeatInfo) (int64, error) {
log.Printf("[INFO] namespaceId:<%s> sending beat to server:<%s> \n", proxy.clientConfig.NamespaceId, utils.ToJsonString(info))
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = info.ServiceName
params["beat"] = utils.ToJsonString(info)
api := constant.SERVICE_BASE_PATH + "/instance/beat"
result, err := proxy.nacosServer.ReqApi(api, params, http.MethodPut)
if err != nil {
return 0, err
}
if result != "" {
interVal, err := jsonparser.GetInt([]byte(result), "clientBeatInterval")
if err != nil {
return 0, errors.New(fmt.Sprintf("[ERROR] namespaceId:<%s> sending beat to server:<%s> get 'clientBeatInterval' from <%s> error:<%s>", proxy.clientConfig.NamespaceId, utils.ToJsonString(info), result, err.Error()))
} else {
return interVal, nil
}
}
return 0, nil
}
func (proxy *NamingProxy) GetServiceList(pageNo int, pageSize int, groupName string, selector *model.ExpressionSelector) (*model.ServiceList, error) {
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["groupName"] = groupName
params["pageNo"] = strconv.Itoa(pageNo)
params["pageSize"] = strconv.Itoa(pageSize)
if selector != nil {
switch selector.Type {
case "label":
params["selector"] = utils.ToJsonString(selector)
break
default:
break
}
}
api := constant.SERVICE_BASE_PATH + "/service/list"
result, err := proxy.nacosServer.ReqApi(api, params, http.MethodGet)
if err != nil {
return nil, err
}
if result == "" {
return nil, errors.New("request server return empty")
}
serviceList := model.ServiceList{}
count, err := jsonparser.GetInt([]byte(result), "count")
if err != nil {
return nil, errors.New(fmt.Sprintf("[ERROR] namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'count' from <%s> error:<%s>", proxy.clientConfig.NamespaceId, pageNo, pageSize, utils.ToJsonString(selector), groupName, result, err.Error()))
}
var doms []string
_, err = jsonparser.ArrayEach([]byte(result), func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
doms = append(doms, string(value))
}, "doms")
if err != nil {
return nil, errors.New(fmt.Sprintf("[ERROR] namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'doms' from <%s> error:<%s> ", proxy.clientConfig.NamespaceId, pageNo, pageSize, utils.ToJsonString(selector), groupName, result, err.Error()))
}
serviceList.Count = count
serviceList.Doms = doms
return &serviceList, nil
}
func (proxy *NamingProxy) ServerHealthy() bool {
api := constant.SERVICE_BASE_PATH + "/operator/metrics"
result, err := proxy.nacosServer.ReqApi(api, map[string]string{}, http.MethodGet)
if err != nil {
log.Printf("[ERROR]:namespaceId:[%s] sending server healthy failed!,result:%s error:%s", proxy.clientConfig.NamespaceId, result, err.Error())
return false
}
if result != "" {
status, err := jsonparser.GetString([]byte(result), "status")
if err != nil {
log.Printf("[ERROR]:namespaceId:[%s] sending server healthy failed!,result:%s error:%s", proxy.clientConfig.NamespaceId, result, err.Error())
} else {
return status == "UP"
}
}
return false
}
func (proxy *NamingProxy) QueryList(serviceName string, clusters string, udpPort int, healthyOnly bool) (string, error) {
param := make(map[string]string)
param["namespaceId"] = proxy.clientConfig.NamespaceId
param["serviceName"] = serviceName
param["clusters"] = clusters
param["udpPort"] = strconv.Itoa(udpPort)
param["healthyOnly"] = strconv.FormatBool(healthyOnly)
param["clientIp"] = utils.LocalIP()
api := constant.SERVICE_PATH + "/list"
return proxy.nacosServer.ReqApi(api, param, http.MethodGet)
}
func (proxy *NamingProxy) GetAllServiceInfoList(namespace string, groupName string, clusters string) (string, error) {
param := make(map[string]string)
param["namespaceId"] = proxy.clientConfig.NamespaceId
param["clusters"] = clusters
param["groupName"] = groupName
api := constant.SERVICE_INFO_PATH + "/getAll"
return proxy.nacosServer.ReqApi(api, param, http.MethodGet)
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 1999-2020 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.
*/
package naming_proxy
import (
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
// INamingProxy ...
type INamingProxy interface {
RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error)
BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error)
DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error)
GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error)
ServerHealthy() bool
QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error)
Subscribe(serviceName, groupName, clusters string) (model.Service, error)
Unsubscribe(serviceName, groupName, clusters string) error
CloseClient()
}

View File

@ -0,0 +1,165 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: clients/naming_client/naming_proxy/proxy_interface.go
// Package naming_proxy is a generated GoMock package.
package naming_proxy
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
model "github.com/nacos-group/nacos-sdk-go/v2/model"
)
// MockINamingProxy is a mock of INamingProxy interface.
type MockINamingProxy struct {
ctrl *gomock.Controller
recorder *MockINamingProxyMockRecorder
}
// MockINamingProxyMockRecorder is the mock recorder for MockINamingProxy.
type MockINamingProxyMockRecorder struct {
mock *MockINamingProxy
}
// NewMockINamingProxy creates a new mock instance.
func NewMockINamingProxy(ctrl *gomock.Controller) *MockINamingProxy {
mock := &MockINamingProxy{ctrl: ctrl}
mock.recorder = &MockINamingProxyMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockINamingProxy) EXPECT() *MockINamingProxyMockRecorder {
return m.recorder
}
// BatchRegisterInstance mocks base method.
func (m *MockINamingProxy) BatchRegisterInstance(serviceName, groupName string, instances []model.Instance) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BatchRegisterInstance", serviceName, groupName, instances)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BatchRegisterInstance indicates an expected call of BatchRegisterInstance.
func (mr *MockINamingProxyMockRecorder) BatchRegisterInstance(serviceName, groupName, instances interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchRegisterInstance", reflect.TypeOf((*MockINamingProxy)(nil).BatchRegisterInstance), serviceName, groupName, instances)
}
// CloseClient mocks base method.
func (m *MockINamingProxy) CloseClient() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "CloseClient")
}
// CloseClient indicates an expected call of CloseClient.
func (mr *MockINamingProxyMockRecorder) CloseClient() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseClient", reflect.TypeOf((*MockINamingProxy)(nil).CloseClient))
}
// DeregisterInstance mocks base method.
func (m *MockINamingProxy) DeregisterInstance(serviceName, groupName string, instance model.Instance) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeregisterInstance", serviceName, groupName, instance)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DeregisterInstance indicates an expected call of DeregisterInstance.
func (mr *MockINamingProxyMockRecorder) DeregisterInstance(serviceName, groupName, instance interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeregisterInstance", reflect.TypeOf((*MockINamingProxy)(nil).DeregisterInstance), serviceName, groupName, instance)
}
// GetServiceList mocks base method.
func (m *MockINamingProxy) GetServiceList(pageNo, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetServiceList", pageNo, pageSize, groupName, namespaceId, selector)
ret0, _ := ret[0].(model.ServiceList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetServiceList indicates an expected call of GetServiceList.
func (mr *MockINamingProxyMockRecorder) GetServiceList(pageNo, pageSize, groupName, namespaceId, selector interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceList", reflect.TypeOf((*MockINamingProxy)(nil).GetServiceList), pageNo, pageSize, groupName, namespaceId, selector)
}
// QueryInstancesOfService mocks base method.
func (m *MockINamingProxy) QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryInstancesOfService", serviceName, groupName, clusters, udpPort, healthyOnly)
ret0, _ := ret[0].(*model.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryInstancesOfService indicates an expected call of QueryInstancesOfService.
func (mr *MockINamingProxyMockRecorder) QueryInstancesOfService(serviceName, groupName, clusters, udpPort, healthyOnly interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryInstancesOfService", reflect.TypeOf((*MockINamingProxy)(nil).QueryInstancesOfService), serviceName, groupName, clusters, udpPort, healthyOnly)
}
// RegisterInstance mocks base method.
func (m *MockINamingProxy) RegisterInstance(serviceName, groupName string, instance model.Instance) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RegisterInstance", serviceName, groupName, instance)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RegisterInstance indicates an expected call of RegisterInstance.
func (mr *MockINamingProxyMockRecorder) RegisterInstance(serviceName, groupName, instance interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterInstance", reflect.TypeOf((*MockINamingProxy)(nil).RegisterInstance), serviceName, groupName, instance)
}
// ServerHealthy mocks base method.
func (m *MockINamingProxy) ServerHealthy() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ServerHealthy")
ret0, _ := ret[0].(bool)
return ret0
}
// ServerHealthy indicates an expected call of ServerHealthy.
func (mr *MockINamingProxyMockRecorder) ServerHealthy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServerHealthy", reflect.TypeOf((*MockINamingProxy)(nil).ServerHealthy))
}
// Subscribe mocks base method.
func (m *MockINamingProxy) Subscribe(serviceName, groupName, clusters string) (model.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Subscribe", serviceName, groupName, clusters)
ret0, _ := ret[0].(model.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Subscribe indicates an expected call of Subscribe.
func (mr *MockINamingProxyMockRecorder) Subscribe(serviceName, groupName, clusters interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockINamingProxy)(nil).Subscribe), serviceName, groupName, clusters)
}
// Unsubscribe mocks base method.
func (m *MockINamingProxy) Unsubscribe(serviceName, groupName, clusters string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Unsubscribe", serviceName, groupName, clusters)
ret0, _ := ret[0].(error)
return ret0
}
// Unsubscribe indicates an expected call of Unsubscribe.
func (mr *MockINamingProxyMockRecorder) Unsubscribe(serviceName, groupName, clusters interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unsubscribe", reflect.TypeOf((*MockINamingProxy)(nil).Unsubscribe), serviceName, groupName, clusters)
}

View File

@ -0,0 +1,139 @@
/*
* Copyright 1999-2020 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.
*/
package naming_client
import (
"context"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_grpc"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_http"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
// NamingProxyDelegate ...
type NamingProxyDelegate struct {
httpClientProxy *naming_http.NamingHttpProxy
grpcClientProxy *naming_grpc.NamingGrpcProxy
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
func NewNamingProxyDelegate(ctx context.Context, clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig,
httpAgent http_agent.IHttpAgent, serviceInfoHolder *naming_cache.ServiceInfoHolder) (naming_proxy.INamingProxy, error) {
return NewNamingProxyDelegateWithRamCredentialProvider(ctx, clientCfg, serverCfgs, httpAgent, serviceInfoHolder, nil)
}
func NewNamingProxyDelegateWithRamCredentialProvider(ctx context.Context, clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig,
httpAgent http_agent.IHttpAgent, serviceInfoHolder *naming_cache.ServiceInfoHolder, provider security.RamCredentialProvider) (naming_proxy.INamingProxy, error) {
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
namingHeader := map[string][]string{
"Client-Version": {constant.CLIENT_VERSION},
"User-Agent": {constant.CLIENT_VERSION},
"RequestId": {uid.String()},
"Request-Module": {"Naming"},
}
nacosServer, err := nacos_server.NewNacosServerWithRamCredentialProvider(ctx, serverCfgs, clientCfg, httpAgent, clientCfg.TimeoutMs, clientCfg.Endpoint, namingHeader, provider)
if err != nil {
return nil, err
}
httpClientProxy, err := naming_http.NewNamingHttpProxy(ctx, clientCfg, nacosServer, serviceInfoHolder)
if err != nil {
return nil, err
}
grpcClientProxy, err := naming_grpc.NewNamingGrpcProxy(ctx, clientCfg, nacosServer, serviceInfoHolder)
if err != nil {
return nil, err
}
return &NamingProxyDelegate{
httpClientProxy: httpClientProxy,
grpcClientProxy: grpcClientProxy,
serviceInfoHolder: serviceInfoHolder,
}, nil
}
func (proxy *NamingProxyDelegate) getExecuteClientProxy(instance model.Instance) (namingProxy naming_proxy.INamingProxy) {
if instance.Ephemeral {
namingProxy = proxy.grpcClientProxy
} else {
namingProxy = proxy.httpClientProxy
}
return namingProxy
}
func (proxy *NamingProxyDelegate) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return proxy.getExecuteClientProxy(instance).RegisterInstance(serviceName, groupName, instance)
}
func (proxy *NamingProxyDelegate) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
return proxy.grpcClientProxy.BatchRegisterInstance(serviceName, groupName, instances)
}
func (proxy *NamingProxyDelegate) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return proxy.getExecuteClientProxy(instance).DeregisterInstance(serviceName, groupName, instance)
}
func (proxy *NamingProxyDelegate) GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
return proxy.grpcClientProxy.GetServiceList(pageNo, pageSize, groupName, namespaceId, selector)
}
func (proxy *NamingProxyDelegate) ServerHealthy() bool {
return proxy.grpcClientProxy.ServerHealthy() || proxy.httpClientProxy.ServerHealthy()
}
func (proxy *NamingProxyDelegate) QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
return proxy.grpcClientProxy.QueryInstancesOfService(serviceName, groupName, clusters, udpPort, healthyOnly)
}
func (proxy *NamingProxyDelegate) Subscribe(serviceName, groupName string, clusters string) (model.Service, error) {
var err error
isSubscribed := proxy.grpcClientProxy.IsSubscribed(serviceName, groupName, clusters)
serviceNameWithGroup := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
serviceInfo, ok := proxy.serviceInfoHolder.ServiceInfoMap.Load(serviceNameWithGroup)
if !isSubscribed || !ok {
serviceInfo, err = proxy.grpcClientProxy.Subscribe(serviceName, groupName, clusters)
if err != nil {
return model.Service{}, err
}
}
service := serviceInfo.(model.Service)
proxy.serviceInfoHolder.ProcessService(&service)
return service, nil
}
func (proxy *NamingProxyDelegate) Unsubscribe(serviceName, groupName, clusters string) error {
proxy.serviceInfoHolder.StopUpdateIfContain(util.GetGroupName(serviceName, groupName), clusters)
return proxy.grpcClientProxy.Unsubscribe(serviceName, groupName, clusters)
}
func (proxy *NamingProxyDelegate) CloseClient() {
proxy.grpcClientProxy.CloseClient()
}

View File

@ -1,115 +0,0 @@
package naming_client
import (
"encoding/json"
"github.com/nacos-group/nacos-sdk-go/utils"
"log"
"math/rand"
"net"
"os"
"strconv"
"time"
)
type PushReceiver struct {
port int
host string
hostReactor *HostReactor
}
type PushData struct {
PushType string `json:"type"`
Data string `json:"data"`
LastRefTime int64 `json:"lastRefTime"`
}
func NewPushRecevier(hostReactor *HostReactor) *PushReceiver {
pr := PushReceiver{
hostReactor: hostReactor,
}
go pr.startServer()
return &pr
}
func (us *PushReceiver) tryListen() (*net.UDPConn, bool) {
addr, err := net.ResolveUDPAddr("udp", us.host+":"+strconv.Itoa(us.port))
if err != nil {
log.Printf("[ERROR]: Can't resolve address,err: %s \n", err.Error())
return nil, false
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Printf("Error listening %s:%d,err:%s \n", us.host, us.port, err.Error())
return nil, false
}
return conn, true
}
func (us *PushReceiver) startServer() {
var conn *net.UDPConn
for i := 0; i < 3; i++ {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
port := r.Intn(1000) + 54951
us.port = port
conn1, ok := us.tryListen()
if ok {
conn = conn1
log.Println("[INFO] udp server start, port: " + strconv.Itoa(port))
break
}
if !ok && i == 2 {
log.Panicf("failed to start udp server after trying 3 times.")
os.Exit(1)
}
}
defer conn.Close()
for {
us.handleClient(conn)
}
}
func (us *PushReceiver) handleClient(conn *net.UDPConn) {
data := make([]byte, 4024)
n, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
log.Printf("[ERROR]:failed to read UDP msg because of %s \n", err.Error())
return
}
s := utils.TryDecompressData(data[:n])
log.Println("[INFO] receive push: "+s+" from: ", remoteAddr)
var pushData PushData
err1 := json.Unmarshal([]byte(s), &pushData)
if err1 != nil {
log.Printf("[ERROR] failed to process push data.err:%s \n", err1.Error())
return
}
ack := make(map[string]string)
if pushData.PushType == "dom" || pushData.PushType == "service" {
us.hostReactor.ProcessServiceJson(pushData.Data)
ack["type"] = "push-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = ""
} else if pushData.PushType == "dump" {
ack["type"] = "dump-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = utils.ToJsonString(us.hostReactor.serviceInfoMap)
} else {
ack["type"] = "unknow-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = ""
}
bs, _ := json.Marshal(ack)
conn.WriteToUDP(bs, remoteAddr)
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 1999-2020 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.
*/
package naming_client
import (
"context"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type ServiceInfoUpdater struct {
ctx context.Context
serviceInfoHolder *naming_cache.ServiceInfoHolder
updateThreadNum int
namingProxy naming_proxy.INamingProxy
}
func NewServiceInfoUpdater(ctx context.Context, serviceInfoHolder *naming_cache.ServiceInfoHolder, updateThreadNum int,
namingProxy naming_proxy.INamingProxy) *ServiceInfoUpdater {
return &ServiceInfoUpdater{
ctx: ctx,
serviceInfoHolder: serviceInfoHolder,
updateThreadNum: updateThreadNum,
namingProxy: namingProxy,
}
}
func (s *ServiceInfoUpdater) asyncUpdateService() {
sema := util.NewSemaphore(s.updateThreadNum)
for {
select {
case <-s.ctx.Done():
return
default:
s.serviceInfoHolder.ServiceInfoMap.Range(func(key, value interface{}) bool {
service := value.(model.Service)
lastRefTime, ok := s.serviceInfoHolder.UpdateTimeMap.Load(util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName),
service.Clusters))
if !ok {
lastRefTime = uint64(0)
}
if uint64(util.CurrentMillis())-lastRefTime.(uint64) > service.CacheMillis {
sema.Acquire()
go func() {
defer sema.Release()
s.updateServiceNow(service.Name, service.GroupName, service.Clusters)
}()
}
return true
})
time.Sleep(1 * time.Second)
}
}
}
func (s *ServiceInfoUpdater) updateServiceNow(serviceName, groupName, clusters string) {
result, err := s.namingProxy.QueryInstancesOfService(serviceName, groupName, clusters, 0, false)
if err != nil {
logger.Errorf("QueryInstances error, serviceName:%s, cluster:%s, err:%v", serviceName, clusters, err)
return
}
s.serviceInfoHolder.ProcessService(result)
}

View File

@ -1,78 +0,0 @@
package naming_client
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"log"
)
type SubscribeCallback struct {
callbackFuncsMap cache.ConcurrentMap
}
func NewSubscribeCallback() SubscribeCallback {
ed := SubscribeCallback{}
ed.callbackFuncsMap = cache.NewConcurrentMap()
return ed
}
func (ed *SubscribeCallback) AddCallbackFuncs(serviceName string, clusters string, callbackFunc *func(services []model.SubscribeService, err error)) {
log.Printf("[INFO] adding " + serviceName + " with " + clusters + " to listener map")
key := utils.GetServiceCacheKey(serviceName, clusters)
var funcs []*func(services []model.SubscribeService, err error)
old, ok := ed.callbackFuncsMap.Get(key)
if ok {
funcs = append(funcs, old.([]*func(services []model.SubscribeService, err error))...)
}
funcs = append(funcs, callbackFunc)
ed.callbackFuncsMap.Set(key, funcs)
}
func (ed *SubscribeCallback) RemoveCallbackFuncs(serviceName string, clusters string, callbackFunc *func(services []model.SubscribeService, err error)) {
log.Printf("[INFO] removing " + serviceName + " with " + clusters + " to listener map")
key := utils.GetServiceCacheKey(serviceName, clusters)
funcs, ok := ed.callbackFuncsMap.Get(key)
if ok && funcs != nil {
var newFuncs []*func(services []model.SubscribeService, err error)
for _, funcItem := range funcs.([]*func(services []model.SubscribeService, err error)) {
if funcItem != callbackFunc {
newFuncs = append(newFuncs, funcItem)
}
}
ed.callbackFuncsMap.Set(key, newFuncs)
}
}
func (ed *SubscribeCallback) ServiceChanged(service *model.Service) {
if service == nil || service.Name == "" {
return
}
key := utils.GetServiceCacheKey(service.Name, service.Clusters)
funcs, ok := ed.callbackFuncsMap.Get(key)
if ok {
for _, funcItem := range funcs.([]*func(services []model.SubscribeService, err error)) {
var subscribeServices []model.SubscribeService
if len(service.Hosts) == 0 {
(*funcItem)(subscribeServices, errors.New("[client.Subscribe] subscribe failed,hosts is empty"))
return
}
for _, host := range service.Hosts {
var subscribeService model.SubscribeService
subscribeService.Valid = host.Valid
subscribeService.Port = host.Port
subscribeService.Ip = host.Ip
subscribeService.Metadata = service.Metadata
subscribeService.ServiceName = host.ServiceName
subscribeService.ClusterName = host.ClusterName
subscribeService.Weight = host.Weight
subscribeService.InstanceId = host.InstanceId
subscribeService.Enable = host.Enable
subscribeServices = append(subscribeServices, subscribeService)
}
(*funcItem)(subscribeServices, nil)
}
}
}

View File

@ -1,163 +0,0 @@
package naming_client
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/stretchr/testify/assert"
"log"
"strings"
"testing"
"time"
)
func TestEventDispatcher_AddCallbackFuncs(t *testing.T) {
service := model.Service{
Dom: "public@@Test",
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Valid: true,
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
fmt.Println(utils.ToJsonString(ed.callbackFuncsMap))
},
}
ed.AddCallbackFuncs(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
key := utils.GetServiceCacheKey(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncsMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*func(services []model.SubscribeService, err error))
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0], &param.SubscribeCallback, "callback function must be equal!")
}
}
func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
service := model.Service{
Dom: "public@@Test",
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Valid: true,
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
fmt.Printf("func1:%s \n", utils.ToJsonString(services))
},
}
ed.AddCallbackFuncs(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
assert.Equal(t, len(ed.callbackFuncsMap.Items()), 1, "callback funcs map length should be 1")
param2 := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
fmt.Printf("func2:%s \n", utils.ToJsonString(services))
},
}
ed.AddCallbackFuncs(utils.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
assert.Equal(t, len(ed.callbackFuncsMap.Items()), 1, "callback funcs map length should be 2")
for k, v := range ed.callbackFuncsMap.Items() {
log.Printf("key:%s,%d", k, len(v.([]*func(services []model.SubscribeService, err error))))
}
ed.RemoveCallbackFuncs(utils.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
key := utils.GetServiceCacheKey(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncsMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*func(services []model.SubscribeService, err error))
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0], &param.SubscribeCallback, "callback function must be equal!")
}
}
func TestSubscribeCallback_ServiceChanged(t *testing.T) {
service := model.Service{
Name: "public@@Test",
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Valid: true,
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("func1:%s \n", utils.ToJsonString(services))
},
}
ed.AddCallbackFuncs(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
param2 := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("func2:%s \n", utils.ToJsonString(services))
},
}
ed.AddCallbackFuncs(utils.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
ed.ServiceChanged(&service)
}

View File

@ -0,0 +1,246 @@
/*
* Copyright 1999-2020 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.
*/
package constant
import (
"os"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
)
func NewClientConfig(opts ...ClientOption) *ClientConfig {
clientConfig := &ClientConfig{
TimeoutMs: 10 * 1000,
BeatInterval: 5 * 1000,
OpenKMS: false,
CacheDir: file.GetCurrentPath() + string(os.PathSeparator) + "cache",
UpdateThreadNum: 20,
NotLoadCacheAtStart: false,
UpdateCacheWhenEmpty: false,
LogDir: file.GetCurrentPath() + string(os.PathSeparator) + "log",
LogLevel: "info",
}
for _, opt := range opts {
opt(clientConfig)
}
return clientConfig
}
// ClientOption ...
type ClientOption func(*ClientConfig)
// WithTimeoutMs ...
func WithTimeoutMs(timeoutMs uint64) ClientOption {
return func(config *ClientConfig) {
config.TimeoutMs = timeoutMs
}
}
// WithAppName ...
func WithAppName(appName string) ClientOption {
return func(config *ClientConfig) {
config.AppName = appName
}
}
// WithBeatInterval ...
func WithBeatInterval(beatInterval int64) ClientOption {
return func(config *ClientConfig) {
config.BeatInterval = beatInterval
}
}
// WithNamespaceId ...
func WithNamespaceId(namespaceId string) ClientOption {
return func(config *ClientConfig) {
config.NamespaceId = namespaceId
}
}
// WithEndpoint ...
func WithEndpoint(endpoint string) ClientOption {
return func(config *ClientConfig) {
config.Endpoint = endpoint
}
}
// WithEndpointContextPath ...
func WithEndpointContextPath(endpointContextPath string) ClientOption {
return func(config *ClientConfig) {
config.EndpointContextPath = endpointContextPath
}
}
// WithEndpointQueryParams ...
func WithEndpointQueryParams(endpointQueryPrams string) ClientOption {
return func(config *ClientConfig) {
config.EndpointQueryParams = endpointQueryPrams
}
}
// WithClusterName ...
func WithClusterName(clusterName string) ClientOption {
return func(config *ClientConfig) {
config.ClusterName = clusterName
}
}
// WithRegionId ...
func WithRegionId(regionId string) ClientOption {
return func(config *ClientConfig) {
config.RegionId = regionId
}
}
// WithAccessKey ...
func WithAccessKey(accessKey string) ClientOption {
return func(config *ClientConfig) {
config.AccessKey = accessKey
}
}
// WithSecretKey ...
func WithSecretKey(secretKey string) ClientOption {
return func(config *ClientConfig) {
config.SecretKey = secretKey
}
}
func WithRamConfig(ramConfig *RamConfig) ClientOption {
return func(config *ClientConfig) {
config.RamConfig = ramConfig
}
}
// WithOpenKMS ...
func WithOpenKMS(openKMS bool) ClientOption {
return func(config *ClientConfig) {
config.OpenKMS = openKMS
}
}
// WithOpenKMS ...
func WithKMSVersion(kmsVersion KMSVersion) ClientOption {
return func(config *ClientConfig) {
config.KMSVersion = kmsVersion
}
}
func WithKMSv3Config(kmsv3Config *KMSv3Config) ClientOption {
return func(config *ClientConfig) {
config.KMSv3Config = kmsv3Config
}
}
func WithKMSConfig(kmsConfig *KMSConfig) ClientOption {
return func(config *ClientConfig) {
config.KMSConfig = kmsConfig
}
}
// WithCacheDir ...
func WithCacheDir(cacheDir string) ClientOption {
return func(config *ClientConfig) {
config.CacheDir = cacheDir
}
}
// WithDisableUseSnapShot ...
func WithDisableUseSnapShot(disableUseSnapShot bool) ClientOption {
return func(config *ClientConfig) {
config.DisableUseSnapShot = disableUseSnapShot
}
}
// WithUpdateThreadNum ...
func WithUpdateThreadNum(updateThreadNum int) ClientOption {
return func(config *ClientConfig) {
config.UpdateThreadNum = updateThreadNum
}
}
// WithNotLoadCacheAtStart ...
func WithNotLoadCacheAtStart(notLoadCacheAtStart bool) ClientOption {
return func(config *ClientConfig) {
config.NotLoadCacheAtStart = notLoadCacheAtStart
}
}
// WithUpdateCacheWhenEmpty ...
func WithUpdateCacheWhenEmpty(updateCacheWhenEmpty bool) ClientOption {
return func(config *ClientConfig) {
config.UpdateCacheWhenEmpty = updateCacheWhenEmpty
}
}
// WithUsername ...
func WithUsername(username string) ClientOption {
return func(config *ClientConfig) {
config.Username = username
}
}
// WithPassword ...
func WithPassword(password string) ClientOption {
return func(config *ClientConfig) {
config.Password = password
}
}
// WithLogDir ...
func WithLogDir(logDir string) ClientOption {
return func(config *ClientConfig) {
config.LogDir = logDir
}
}
// WithLogLevel ...
func WithLogLevel(logLevel string) ClientOption {
return func(config *ClientConfig) {
config.LogLevel = logLevel
}
}
// WithLogSampling ...
func WithLogSampling(tick time.Duration, initial int, thereafter int) ClientOption {
return func(config *ClientConfig) {
config.LogSampling = &ClientLogSamplingConfig{initial, thereafter, tick}
}
}
// WithLogRollingConfig ...
func WithLogRollingConfig(rollingConfig *ClientLogRollingConfig) ClientOption {
return func(config *ClientConfig) {
config.LogRollingConfig = rollingConfig
}
}
func WithTLS(tlsCfg TLSConfig) ClientOption {
return func(config *ClientConfig) {
tlsCfg.Appointed = true
config.TLSCfg = tlsCfg
}
}
func WithAppConnLabels(appConnLabels map[string]string) ClientOption {
return func(config *ClientConfig) {
config.AppConnLabels = appConnLabels
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 1999-2020 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.
*/
package constant
import (
"os"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/stretchr/testify/assert"
)
func TestNewClientConfig(t *testing.T) {
config := NewClientConfig()
assert.Equal(t, config.TimeoutMs, uint64(10000))
assert.Equal(t, config.Endpoint, "")
assert.Equal(t, config.LogLevel, "info")
assert.Equal(t, config.BeatInterval, int64(5000))
assert.Equal(t, config.UpdateThreadNum, 20)
assert.Equal(t, config.LogDir, file.GetCurrentPath()+string(os.PathSeparator)+"log")
assert.Equal(t, config.CacheDir, file.GetCurrentPath()+string(os.PathSeparator)+"cache")
assert.Equal(t, config.NotLoadCacheAtStart, false)
assert.Equal(t, config.UpdateCacheWhenEmpty, false)
assert.Equal(t, config.Username, "")
assert.Equal(t, config.Password, "")
assert.Equal(t, config.OpenKMS, false)
assert.Equal(t, config.NamespaceId, "")
assert.Equal(t, config.Username, "")
assert.Equal(t, config.RegionId, "")
assert.Equal(t, config.AccessKey, "")
assert.Equal(t, config.SecretKey, "")
}
func TestNewClientConfigWithOptions(t *testing.T) {
config := NewClientConfig(
WithTimeoutMs(uint64(20000)),
WithEndpoint("http://console.nacos.io:80"),
WithLogLevel("error"),
WithBeatInterval(int64(2000)),
WithUpdateThreadNum(30),
WithLogDir("/tmp/nacos/log"),
WithCacheDir("/tmp/nacos/cache"),
WithNotLoadCacheAtStart(true),
WithUpdateCacheWhenEmpty(true),
WithUsername("nacos"),
WithPassword("nacos"),
WithOpenKMS(true),
WithRegionId("shanghai"),
WithNamespaceId("namespace_1"),
WithAccessKey("accessKey_1"),
WithSecretKey("secretKey_1"),
)
assert.Equal(t, config.TimeoutMs, uint64(20000))
assert.Equal(t, config.Endpoint, "http://console.nacos.io:80")
assert.Equal(t, config.LogLevel, "error")
assert.Equal(t, config.BeatInterval, int64(2000))
assert.Equal(t, config.UpdateThreadNum, 30)
assert.Equal(t, config.LogDir, "/tmp/nacos/log")
assert.Equal(t, config.CacheDir, "/tmp/nacos/cache")
assert.Equal(t, config.NotLoadCacheAtStart, true)
assert.Equal(t, config.UpdateCacheWhenEmpty, true)
assert.Equal(t, config.Username, "nacos")
assert.Equal(t, config.Password, "nacos")
assert.Equal(t, config.OpenKMS, true)
assert.Equal(t, config.RegionId, "shanghai")
assert.Equal(t, config.NamespaceId, "namespace_1")
assert.Equal(t, config.AccessKey, "accessKey_1")
assert.Equal(t, config.SecretKey, "secretKey_1")
}

View File

@ -1,33 +1,134 @@
/*
* Copyright 1999-2020 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.
*/
package constant
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-07 15:13
**/
import "time"
type ServerConfig struct {
ContextPath string
IpAddr string
Port uint64
Scheme string // the nacos server scheme,default=http,this is not required in 2.0
ContextPath string // the nacos server contextpath,default=/nacos,this is not required in 2.0
IpAddr string // the nacos server address
Port uint64 // nacos server port
GrpcPort uint64 // nacos server grpc port, default=server port + 1000, this is not required
}
type ClientConfig struct {
TimeoutMs uint64
ListenInterval uint64
BeatInterval int64
NamespaceId string
Endpoint string
AccessKey string
SecretKey string
CacheDir string
LogDir string
UpdateThreadNum int
NotLoadCacheAtStart bool
UpdateCacheWhenEmpty bool
OpenKMS bool
RegionId string
TimeoutMs uint64 // timeout for requesting Nacos server, default value is 10000ms
ListenInterval uint64 // Deprecated
BeatInterval int64 // the time interval for sending beat to server,default value is 5000ms
NamespaceId string // the namespaceId of Nacos.When namespace is public, fill in the blank string here.
AppName string // the appName
AppKey string // the client identity information
Endpoint string // the endpoint for get Nacos server addresses
RegionId string // the regionId for kms
AccessKey string // the AccessKey for kms
SecretKey string // the SecretKey for kms
RamConfig *RamConfig
OpenKMS bool // it's to open kms, default is false. https://help.aliyun.com/product/28933.html
KMSVersion KMSVersion // kms client version. https://help.aliyun.com/document_detail/380927.html
KMSv3Config *KMSv3Config //KMSv3 configuration. https://help.aliyun.com/document_detail/601596.html
KMSConfig *KMSConfig
CacheDir string // the directory for persist nacos service info,default value is current path
DisableUseSnapShot bool // It's a switch, default is false, means that when get remote config fail, use local cache file instead
UpdateThreadNum int // the number of goroutine for update nacos service info,default value is 20
NotLoadCacheAtStart bool // not to load persistent nacos service info in CacheDir at start time
UpdateCacheWhenEmpty bool // update cache when get empty service instance from server
Username string // the username for nacos auth
Password string // the password for nacos auth
LogDir string // the directory for log, default is current path
LogLevel string // the level of log, it's must be debug,info,warn,error, default value is info
ContextPath string // the nacos server contextpath
AppendToStdout bool // if append log to stdout
LogSampling *ClientLogSamplingConfig // the sampling config of log
LogRollingConfig *ClientLogRollingConfig // log rolling config
TLSCfg TLSConfig // tls Config
AsyncUpdateService bool // open async update service by query
EndpointContextPath string // the address server endpoint contextPath
EndpointQueryParams string // the address server endpoint query params
ClusterName string // the address server clusterName
AppConnLabels map[string]string // app conn labels
}
type ClientLogSamplingConfig struct {
Initial int //the sampling initial of log
Thereafter int //the sampling thereafter of log
Tick time.Duration //the sampling tick of log
}
type ClientLogRollingConfig struct {
// MaxSize is the maximum size in megabytes of the log file before it gets
// rotated. It defaults to 100 megabytes.
MaxSize int
// MaxAge is the maximum number of days to retain old log files based on the
// timestamp encoded in their filename. Note that a day is defined as 24
// hours and may not exactly correspond to calendar days due to daylight
// savings, leap seconds, etc. The default is not to remove old log files
// based on age.
MaxAge int
// MaxBackups is the maximum number of old log files to retain. The default
// is to retain all old log files (though MaxAge may still cause them to get
// deleted.)
MaxBackups int
// LocalTime determines if the time used for formatting the timestamps in
// backup files is the computer's local time. The default is to use UTC
// time.
LocalTime bool
// Compress determines if the rotated log files should be compressed
// using gzip. The default is not to perform compression.
Compress bool
}
type TLSConfig struct {
Appointed bool // Appointed or not ,if false,will get from env.
Enable bool // enable tls
TrustAll bool // trust all server
CaFile string // clients use when verifying server certificates
CertFile string // server use when verifying client certificates
KeyFile string // server use when verifying client certificates
ServerNameOverride string // serverNameOverride is for testing only
}
type KMSv3Config struct {
ClientKeyContent string
Password string
Endpoint string
CaContent string
}
type KMSConfig struct {
Endpoint string
OpenSSL string
CaContent string
}
type RamConfig struct {
SecurityToken string
SignatureRegionId string
RamRoleName string
RoleArn string
Policy string
RoleSessionName string
RoleSessionExpiration int
OIDCProviderArn string
OIDCTokenFilePath string
CredentialsURI string
SecretName string
}

View File

@ -1,62 +1,121 @@
/*
* Copyright 1999-2020 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.
*/
package constant
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-07 15:13
**/
import "time"
type KMSVersion string
const (
KEY_ENDPOINT = "endpoint"
KEY_NAME_SPACE = "namespace"
KEY_ACCESS_KEY = "accessKey"
KEY_SECRET_KEY = "secretKey"
KEY_SERVER_ADDR = "serverAddr"
KEY_CONTEXT_PATH = "contextPath"
KEY_ENCODE = "encode"
KEY_DATA_ID = "dataId"
KEY_GROUP = "group"
KEY_TENANT = "tenant"
KEY_DESC = "desc"
KEY_APP_NAME = "appName"
KEY_CONTENT = "content"
KEY_TIMEOUT_MS = "timeoutMs"
KEY_LISTEN_INTERVAL = "listenInterval"
KEY_SERVER_CONFIGS = "serverConfigs"
KEY_CLIENT_CONFIG = "clientConfig"
WEB_CONTEXT = "/nacos"
CONFIG_BASE_PATH = "/v1/cs"
CONFIG_PATH = CONFIG_BASE_PATH + "/configs"
CONFIG_LISTEN_PATH = CONFIG_BASE_PATH + "/configs/listener"
SERVICE_BASE_PATH = "/v1/ns"
SERVICE_PATH = SERVICE_BASE_PATH + "/instance"
SERVICE_INFO_PATH = SERVICE_BASE_PATH + "/service"
SERVICE_SUBSCRIBE_PATH = SERVICE_PATH + "/list"
NAMESPACE_PATH = "/v1/console/namespaces"
SPLIT_CONFIG = string(rune(1))
SPLIT_CONFIG_INNER = string(rune(2))
KEY_LISTEN_CONFIGS = "Listening-Configs"
KEY_SERVICE_NAME = "serviceName"
KEY_IP = "ip"
KEY_PORT = "port"
KEY_WEIGHT = "weight"
KEY_ENABLE = "enable"
KEY_HEALTHY = "healthy"
KEY_METADATA = "metadata"
KEY_CLUSTER_NAME = "clusterName"
KEY_CLUSTER = "cluster"
KEY_BEAT = "beat"
KEY_DOM = "dom"
DEFAULT_CONTEXT_PATH = "/nacos"
CLIENT_VERSION = "Nacos-go-Client:v1.0.0"
REQUEST_DOMAIN_RETRY_TIME = 3
SERVICE_INFO_SPLITER = "@@"
CONFIG_INFO_SPLITER = "@@"
DEFAULT_NAMESPACE_ID = "public"
DEFAULT_GROUP = "DEFAULT_GROUP"
NAMING_INSTANCE_ID_SPLITTER = "#"
DefaultClientErrorCode = "SDK.NacosError"
KMSv1 KMSVersion = "KMSv1"
KMSv3 KMSVersion = "KMSv3"
DEFAULT_KMS_VERSION KMSVersion = "" //to fit original version
UNKNOWN_KMS_VERSION KMSVersion = "UNKNOWN_KMS_VERSION"
)
const (
KEY_USERNAME = "username"
KEY_PASSWORD = "password"
KEY_ENDPOINT = "endpoint"
KEY_NAME_SPACE = "namespace"
KEY_ACCESS_KEY = "accessKey"
KEY_SECRET_KEY = "secretKey"
KEY_SERVER_ADDR = "serverAddr"
KEY_CONTEXT_PATH = "contextPath"
KEY_ENCODE = "encode"
KEY_DATA_ID = "dataId"
KEY_GROUP = "group"
KEY_TENANT = "tenant"
KEY_DESC = "desc"
KEY_APP_NAME = "appName"
KEY_CONTENT = "content"
KEY_TIMEOUT_MS = "timeoutMs"
KEY_LISTEN_INTERVAL = "listenInterval"
KEY_SERVER_CONFIGS = "serverConfigs"
KEY_CLIENT_CONFIG = "clientConfig"
KEY_TOKEN = "token"
KEY_ACCESS_TOKEN = "accessToken"
KEY_TOKEN_TTL = "tokenTtl"
KEY_GLOBAL_ADMIN = "globalAdmin"
KEY_TOKEN_REFRESH_WINDOW = "tokenRefreshWindow"
WEB_CONTEXT = "/nacos"
CONFIG_BASE_PATH = "/v1/cs"
CONFIG_PATH = CONFIG_BASE_PATH + "/configs"
CONFIG_AGG_PATH = "/datum.do"
CONFIG_LISTEN_PATH = CONFIG_BASE_PATH + "/configs/listener"
SERVICE_BASE_PATH = "/v1/ns"
SERVICE_PATH = SERVICE_BASE_PATH + "/instance"
SERVICE_INFO_PATH = SERVICE_BASE_PATH + "/service"
SERVICE_SUBSCRIBE_PATH = SERVICE_PATH + "/list"
NAMESPACE_PATH = "/v1/console/namespaces"
SPLIT_CONFIG = string(rune(1))
SPLIT_CONFIG_INNER = string(rune(2))
KEY_LISTEN_CONFIGS = "Listening-Configs"
KEY_SERVICE_NAME = "serviceName"
KEY_IP = "ip"
KEY_PORT = "port"
KEY_WEIGHT = "weight"
KEY_ENABLE = "enable"
KEY_HEALTHY = "healthy"
KEY_METADATA = "metadata"
KEY_CLUSTER_NAME = "clusterName"
KEY_CLUSTER = "cluster"
KEY_BEAT = "beat"
KEY_DOM = "dom"
DEFAULT_CONTEXT_PATH = "/nacos"
CLIENT_VERSION = "Nacos-Go-Client:v2.3.3"
REQUEST_DOMAIN_RETRY_TIME = 3
SERVICE_INFO_SPLITER = "@@"
CONFIG_INFO_SPLITER = "@@"
DEFAULT_NAMESPACE_ID = "public"
DEFAULT_GROUP = "DEFAULT_GROUP"
NAMING_INSTANCE_ID_SPLITTER = "#"
DefaultClientErrorCode = "SDK.NacosError"
DEFAULT_SERVER_SCHEME = "http"
HTTPS_SERVER_SCHEME = "https"
LABEL_SOURCE = "source"
LABEL_SOURCE_SDK = "sdk"
LABEL_MODULE = "module"
LABEL_MODULE_CONFIG = "config"
LABEL_MODULE_NAMING = "naming"
RESPONSE_CODE_SUCCESS = 200
UN_REGISTER = 301
KEEP_ALIVE_TIME = 5
DEFAULT_TIMEOUT_MILLS = 3000
ALL_SYNC_INTERNAL = 5 * time.Minute
CLIENT_APPNAME_HEADER = "Client-AppName"
APPNAME_HEADER = "AppName"
CLIENT_REQUEST_TS_HEADER = "Client-RequestTS"
CLIENT_REQUEST_TOKEN_HEADER = "Client-RequestToken"
EX_CONFIG_INFO = "exConfigInfo"
CHARSET_KEY = "charset"
LOG_FILE_NAME = "nacos-sdk.log"
HTTPS_SERVER_PORT = 443
GRPC = "grpc"
RpcPortOffset = 1000
MSE_KMSv1_DEFAULT_KEY_ID = "alias/acs/mse"
CONFIG_PUBLISH_REQUEST_NAME = "ConfigPublishRequest"
CONFIG_QUERY_REQUEST_NAME = "ConfigQueryRequest"
CONFIG_REMOVE_REQUEST_NAME = "ConfigRemoveRequest"
INSTANCE_REQUEST_NAME = "InstanceRequest"
BATCH_INSTANCE_REQUEST_NAME = "BatchInstanceRequest"
SERVICE_LIST_REQUEST_NAME = "ServiceListRequest"
SERVICE_QUERY_REQUEST_NAME = "ServiceQueryRequest"
SUBSCRIBE_SERVICE_REQUEST_NAME = "SubscribeServiceRequest"
NOTIFY_SUBSCRIBE_REQUEST_NAME = "NotifySubscriberRequest"
CONFIG_BATCH_LISTEN_REQUEST_NAME = "ConfigBatchListenRequest"
CONFIG_CHANGE_NOTIFY_REQUEST_NAME = "ConfigChangeNotifyRequest"
)

View File

@ -1,3 +1,19 @@
/*
* Copyright 1999-2020 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.
*/
package constant
const (

View File

@ -0,0 +1,70 @@
/*
* Copyright 1999-2020 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.
*/
package constant
func NewServerConfig(ipAddr string, port uint64, opts ...ServerOption) *ServerConfig {
serverConfig := &ServerConfig{
IpAddr: ipAddr,
Port: port,
ContextPath: DEFAULT_CONTEXT_PATH,
Scheme: DEFAULT_SERVER_SCHEME,
}
for _, opt := range opts {
opt(serverConfig)
}
return serverConfig
}
// ServerOption ...
type ServerOption func(*ServerConfig)
// WithScheme set Scheme for server
func WithScheme(scheme string) ServerOption {
return func(config *ServerConfig) {
config.Scheme = scheme
}
}
// WithContextPath set contextPath for server
func WithContextPath(contextPath string) ServerOption {
return func(config *ServerConfig) {
config.ContextPath = contextPath
}
}
// WithIpAddr set ip address for server
func WithIpAddr(ipAddr string) ServerOption {
return func(config *ServerConfig) {
config.IpAddr = ipAddr
}
}
// WithPort set port for server
func WithPort(port uint64) ServerOption {
return func(config *ServerConfig) {
config.Port = port
}
}
// WithGrpcPort set grpc port for server
func WithGrpcPort(port uint64) ServerOption {
return func(config *ServerConfig) {
config.GrpcPort = port
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 1999-2020 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.
*/
package constant
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewServerConfig(t *testing.T) {
config := NewServerConfig("console.nacos.io", 80)
assert.Equal(t, "console.nacos.io", config.IpAddr)
assert.Equal(t, uint64(80), config.Port)
assert.Equal(t, "/nacos", config.ContextPath)
assert.Equal(t, "http", config.Scheme)
assert.True(t, config.Port > 0 && config.Port < 65535)
}
func TestNewServerConfigWithOptions(t *testing.T) {
config := NewServerConfig(
"console.nacos.io",
80,
WithContextPath("/ns"),
WithScheme("https"),
)
assert.Equal(t, "console.nacos.io", config.IpAddr)
assert.Equal(t, uint64(80), config.Port)
assert.Equal(t, "/ns", config.ContextPath)
assert.Equal(t, "https", config.Scheme)
assert.True(t, config.Port > 0 && config.Port < 65535)
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 1999-2020 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.
*/
package constant
var SkipVerifyConfig = TLSConfig{Enable: true}
func NewTLSConfig(opts ...TLSOption) *TLSConfig {
tlsConfig := TLSConfig{Enable: true}
for _, opt := range opts {
opt(&tlsConfig)
}
return &tlsConfig
}
type TLSOption func(*TLSConfig)
func WithCA(caFile, serverNameOverride string) TLSOption {
return func(tc *TLSConfig) {
tc.CaFile = caFile
tc.ServerNameOverride = serverNameOverride
}
}
func WithCertificate(certFile, keyFile string) TLSOption {
return func(tc *TLSConfig) {
tc.CertFile = certFile
tc.KeyFile = keyFile
}
}

View File

@ -0,0 +1,54 @@
package constant
/*
* Copyright 1999-2020 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.
*/
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewTLSConfigWithOptions(t *testing.T) {
t.Run("TestNoOption", func(t *testing.T) {
cfg := SkipVerifyConfig
assert.Equal(t, "", cfg.CaFile)
assert.Equal(t, "", cfg.CertFile)
assert.Equal(t, "", cfg.KeyFile)
assert.Equal(t, "", cfg.ServerNameOverride)
})
t.Run("TestCAOption", func(t *testing.T) {
cfg := NewTLSConfig(
WithCA("ca", "host"),
)
assert.Equal(t, "ca", cfg.CaFile)
assert.Equal(t, "", cfg.CertFile)
assert.Equal(t, "", cfg.KeyFile)
assert.Equal(t, "host", cfg.ServerNameOverride)
})
t.Run("TestCertOption", func(t *testing.T) {
cfg := NewTLSConfig(
WithCA("ca", "host"),
WithCertificate("cert", "key"),
)
assert.Equal(t, "ca", cfg.CaFile)
assert.Equal(t, "cert", cfg.CertFile)
assert.Equal(t, "key", cfg.KeyFile)
assert.Equal(t, "host", cfg.ServerNameOverride)
})
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 1999-2020 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.
*/
package encoding
import (
"encoding/base64"
"unicode/utf8"
)
func DecodeString2Utf8Bytes(data string) []byte {
resBytes := make([]byte, 0, 8)
if len(data) == 0 {
return resBytes
}
bytesLen := 0
runes := []rune(data)
for _, r := range runes {
bytesLen += utf8.RuneLen(r)
}
resBytes = make([]byte, bytesLen)
pos := 0
for _, r := range runes {
pos += utf8.EncodeRune(resBytes[pos:], r)
}
return resBytes
}
func EncodeUtf8Bytes2String(bytes []byte) string {
if len(bytes) == 0 {
return ""
}
var startPos, endPos int
resRunes := make([]rune, 0, 8)
for endPos <= len(bytes) {
if utf8.FullRune(bytes[startPos:endPos]) {
decodedRune, _ := utf8.DecodeRune(bytes[startPos:endPos])
resRunes = append(resRunes, decodedRune)
startPos = endPos
}
endPos++
}
return string(resRunes)
}
func DecodeBase64(bytes []byte) ([]byte, error) {
dst := make([]byte, base64.StdEncoding.DecodedLen(len(bytes)))
n, err := base64.StdEncoding.Decode(dst, bytes)
if err != nil {
return nil, err
}
return dst[:n], nil
}
func EncodeBase64(bytes []byte) ([]byte, error) {
dst := make([]byte, base64.StdEncoding.EncodedLen(len(bytes)))
base64.StdEncoding.Encode(dst, bytes)
return dst[:], nil
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 1999-2020 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.
*/
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
)
func AesEcbPkcs5PaddingEncrypt(plainContent, key []byte) (retBytes []byte, err error) {
if len(plainContent) == 0 {
return nil, nil
}
aesCipherBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
pkcs5PaddingBytes := PKCS5Padding(plainContent, aesCipherBlock.BlockSize())
return BlockEncrypt(pkcs5PaddingBytes, aesCipherBlock)
}
func AesEcbPkcs5PaddingDecrypt(cipherContent, key []byte) (retBytes []byte, err error) {
if len(cipherContent) == 0 {
return nil, nil
}
aesCipherBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decryptBytes, err := BlockDecrypt(cipherContent, aesCipherBlock)
if err != nil {
return nil, err
}
retBytes = PKCS5UnPadding(decryptBytes)
return retBytes, nil
}
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func BlockEncrypt(src []byte, b cipher.Block) (dst []byte, err error) {
if len(src)%b.BlockSize() != 0 {
return nil, fmt.Errorf("input not full blocks")
}
buf := make([]byte, b.BlockSize())
for i := 0; i < len(src); i += b.BlockSize() {
b.Encrypt(buf, src[i:i+b.BlockSize()])
dst = append(dst, buf...)
}
return
}
func BlockDecrypt(src []byte, b cipher.Block) (dst []byte, err error) {
if len(src)%b.BlockSize() != 0 {
return nil, fmt.Errorf("input not full blocks")
}
buf := make([]byte, b.BlockSize())
for i := 0; i < len(src); i += b.BlockSize() {
b.Decrypt(buf, src[i:i+b.BlockSize()])
dst = append(dst, buf...)
}
return
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 1999-2020 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.
*/
package encryption
import "fmt"
const (
CipherPrefix = "cipher-"
KmsAes128AlgorithmName = "cipher-kms-aes-128"
KmsAes256AlgorithmName = "cipher-kms-aes-256"
KmsAlgorithmName = "cipher"
kmsAes128KeySpec = "AES_128"
kmsAes256KeySpec = "AES_256"
kmsScheme = "https"
kmsAcceptFormat = "XML"
kmsCipherAlgorithm = "AES/ECB/PKCS5Padding"
maskUnit8Width = 8
maskUnit32Width = 32
KmsHandlerName = "KmsHandler"
)
var (
DataIdParamCheckError = fmt.Errorf("dataId prefix should start with: %s", CipherPrefix)
ContentParamCheckError = fmt.Errorf("content need to encrypt is nil")
KeyIdParamCheckError = fmt.Errorf("keyId is nil, need to be set")
)
var (
PluginNotFoundError = fmt.Errorf("cannot find encryption plugin by dataId prefix")
)
var (
EmptyEncryptedDataKeyError = fmt.Errorf("empty encrypted data key error")
EmptyPlainDataKeyError = fmt.Errorf("empty plain data key error")
EmptyContentError = fmt.Errorf("encrypt empty content error")
)
var (
EmptyRegionKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty region")
EmptyAkKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty ak")
EmptySkKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty sk")
EmptyEndpointKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty endpoint")
EmptyPasswordKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty password")
EmptyClientKeyContentKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty client key content")
EmptyCaVerifyKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty ca verify")
EmptyEndpointKmsRamClientInitError = fmt.Errorf("init kmsRam client failed with empty endpoint")
)

View File

@ -0,0 +1,224 @@
/*
* Copyright 1999-2020 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.
*/
package encryption
import (
"fmt"
"github.com/alibabacloud-go/tea/tea"
dkms_api "github.com/aliyun/alibabacloud-dkms-gcs-go-sdk/openapi"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/pkg/errors"
"strings"
)
type HandlerParam struct {
DataId string `json:"dataId"` //required
Content string `json:"content"` //required
EncryptedDataKey string `json:"encryptedDataKey"`
PlainDataKey string `json:"plainDataKey"`
KeyId string `json:"keyId"`
}
type Plugin interface {
Encrypt(*HandlerParam) error
Decrypt(*HandlerParam) error
AlgorithmName() string
GenerateSecretKey(*HandlerParam) (string, error)
EncryptSecretKey(*HandlerParam) (string, error)
DecryptSecretKey(*HandlerParam) (string, error)
}
type Handler interface {
EncryptionHandler(*HandlerParam) error
DecryptionHandler(*HandlerParam) error
RegisterPlugin(Plugin) error
GetHandlerName() string
}
func NewKmsHandler() Handler {
return newKmsHandler()
}
func newKmsHandler() *KmsHandler {
kmsHandler := &KmsHandler{
encryptionPlugins: make(map[string]Plugin, 2),
}
logger.Debug("successfully create encryption KmsHandler")
return kmsHandler
}
func RegisterConfigEncryptionKmsPlugins(encryptionHandler Handler, clientConfig constant.ClientConfig) {
innerKmsClient, err := innerNewKmsClient(clientConfig)
if innerKmsClient == nil {
err = errors.New("create kms client failed.")
}
if err != nil && innerKmsClient == nil {
err = errors.New("create kms client failed.")
}
if err != nil {
logger.Error(err)
}
if err := encryptionHandler.RegisterPlugin(&KmsAes128Plugin{kmsPlugin{kmsClient: innerKmsClient}}); err != nil {
logger.Errorf("failed to register encryption plugin[%s] to %s", KmsAes128AlgorithmName, encryptionHandler.GetHandlerName())
} else {
logger.Debugf("successfully register encryption plugin[%s] to %s", KmsAes128AlgorithmName, encryptionHandler.GetHandlerName())
}
if err := encryptionHandler.RegisterPlugin(&KmsAes256Plugin{kmsPlugin{kmsClient: innerKmsClient}}); err != nil {
logger.Errorf("failed to register encryption plugin[%s] to %s", KmsAes256AlgorithmName, encryptionHandler.GetHandlerName())
} else {
logger.Debugf("successfully register encryption plugin[%s] to %s", KmsAes256AlgorithmName, encryptionHandler.GetHandlerName())
}
if err := encryptionHandler.RegisterPlugin(&KmsBasePlugin{kmsPlugin{kmsClient: innerKmsClient}}); err != nil {
logger.Errorf("failed to register encryption plugin[%s] to %s", KmsAlgorithmName, encryptionHandler.GetHandlerName())
} else {
logger.Debugf("successfully register encryption plugin[%s] to %s", KmsAlgorithmName, encryptionHandler.GetHandlerName())
}
}
type KmsHandler struct {
encryptionPlugins map[string]Plugin
}
func (d *KmsHandler) EncryptionHandler(param *HandlerParam) error {
if err := d.encryptionParamCheck(*param); err != nil {
return err
}
plugin, err := d.getPluginByDataIdPrefix(param.DataId)
if err != nil {
return err
}
plainSecretKey, err := plugin.GenerateSecretKey(param)
if err != nil {
return err
}
param.PlainDataKey = plainSecretKey
return plugin.Encrypt(param)
}
func (d *KmsHandler) DecryptionHandler(param *HandlerParam) error {
if err := d.decryptionParamCheck(*param); err != nil {
return err
}
plugin, err := d.getPluginByDataIdPrefix(param.DataId)
if err != nil {
return err
}
plainSecretkey, err := plugin.DecryptSecretKey(param)
if err != nil {
return err
}
param.PlainDataKey = plainSecretkey
return plugin.Decrypt(param)
}
func (d *KmsHandler) getPluginByDataIdPrefix(dataId string) (Plugin, error) {
var (
matchedCount int
matchedPlugin Plugin
)
for k, v := range d.encryptionPlugins {
if strings.Contains(dataId, k) {
if len(k) > matchedCount {
matchedCount = len(k)
matchedPlugin = v
}
}
}
if matchedPlugin == nil {
return matchedPlugin, PluginNotFoundError
}
return matchedPlugin, nil
}
func (d *KmsHandler) RegisterPlugin(plugin Plugin) error {
if _, v := d.encryptionPlugins[plugin.AlgorithmName()]; v {
logger.Warnf("encryption algorithm [%s] has already registered to defaultHandler, will be update", plugin.AlgorithmName())
} else {
logger.Debugf("register encryption algorithm [%s] to defaultHandler", plugin.AlgorithmName())
}
d.encryptionPlugins[plugin.AlgorithmName()] = plugin
return nil
}
func (d *KmsHandler) GetHandlerName() string {
return KmsHandlerName
}
func (d *KmsHandler) encryptionParamCheck(param HandlerParam) error {
if err := d.dataIdParamCheck(param.DataId); err != nil {
return DataIdParamCheckError
}
if err := d.contentParamCheck(param.Content); err != nil {
return ContentParamCheckError
}
return nil
}
func (d *KmsHandler) decryptionParamCheck(param HandlerParam) error {
return d.encryptionParamCheck(param)
}
func (d *KmsHandler) keyIdParamCheck(keyId string) error {
if len(keyId) == 0 {
return fmt.Errorf("cipher dataId using kmsService need to set keyId, but keyId is nil")
}
return nil
}
func (d *KmsHandler) dataIdParamCheck(dataId string) error {
if !strings.Contains(dataId, CipherPrefix) {
return fmt.Errorf("dataId prefix should start with: %s", CipherPrefix)
}
return nil
}
func (d *KmsHandler) contentParamCheck(content string) error {
if len(content) == 0 {
return fmt.Errorf("content need to encrypt is nil")
}
return nil
}
func innerNewKmsClient(clientConfig constant.ClientConfig) (kmsClient KmsClient, err error) {
switch clientConfig.KMSVersion {
case constant.KMSv1, constant.DEFAULT_KMS_VERSION:
kmsClient, err = newKmsRamClient(clientConfig)
case constant.KMSv3:
kmsClient, err = newKmsV3Client(clientConfig)
default:
err = fmt.Errorf("init kms client failed. unknown kms version:%s\n", clientConfig.KMSVersion)
}
return kmsClient, err
}
func newKmsV1Client(clientConfig constant.ClientConfig) (KmsClient, error) {
return NewKmsV1ClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
}
func newKmsV3Client(clientConfig constant.ClientConfig) (KmsClient, error) {
return NewKmsV3ClientWithConfig(&dkms_api.Config{
Protocol: tea.String("https"),
Endpoint: tea.String(clientConfig.KMSv3Config.Endpoint),
ClientKeyContent: tea.String(clientConfig.KMSv3Config.ClientKeyContent),
Password: tea.String(clientConfig.KMSv3Config.Password),
}, clientConfig.KMSv3Config.CaContent)
}
func newKmsRamClient(clientConfig constant.ClientConfig) (KmsClient, error) {
return NewKmsRamClient(clientConfig.KMSConfig, clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
}

View File

@ -0,0 +1,303 @@
/*
* Copyright 1999-2020 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.
*/
package encryption
import (
"fmt"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
kms20160120 "github.com/alibabacloud-go/kms-20160120/v3/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
dkms_api "github.com/aliyun/alibabacloud-dkms-gcs-go-sdk/openapi"
dkms_transfer "github.com/aliyun/alibabacloud-dkms-transfer-go-sdk/sdk"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/pkg/errors"
"net/http"
"strings"
)
type KmsClient interface {
Decrypt(cipherContent string) (string, error)
Encrypt(content string, keyId string) (string, error)
GenerateDataKey(keyId, keySpec string) (string, string, error)
GetKmsVersion() constant.KMSVersion
setKmsVersion(constant.KMSVersion)
}
type TransferKmsClient struct {
*dkms_transfer.KmsTransferClient
kmsVersion constant.KMSVersion
}
func NewKmsV1ClientWithAccessKey(regionId, ak, sk string) (*TransferKmsClient, error) {
var rErr error
if rErr = checkKmsV1InitParam(regionId, ak, sk); rErr != nil {
return nil, rErr
}
kmsClient, err := newKmsV1ClientWithAccessKey(regionId, ak, sk)
if err != nil {
rErr = errors.Wrap(err, "init kms v1 client with ak/sk failed")
} else {
kmsClient.setKmsVersion(constant.KMSv1)
}
return kmsClient, rErr
}
func checkKmsV1InitParam(regionId, ak, sk string) error {
if len(regionId) == 0 {
return EmptyRegionKmsV1ClientInitError
}
if len(ak) == 0 {
return EmptyAkKmsV1ClientInitError
}
if len(sk) == 0 {
return EmptySkKmsV1ClientInitError
}
return nil
}
func checkKmsRamInitParam(endpoint, ak, sk string) error {
if len(endpoint) == 0 {
return EmptyEndpointKmsRamClientInitError
}
if len(ak) == 0 {
return EmptyAkKmsV1ClientInitError
}
if len(sk) == 0 {
return EmptySkKmsV1ClientInitError
}
return nil
}
func NewKmsV3ClientWithConfig(config *dkms_api.Config, caVerify string) (*TransferKmsClient, error) {
var rErr error
if rErr = checkKmsV3InitParam(config, caVerify); rErr != nil {
return nil, rErr
}
kmsClient, err := newKmsV3ClientWithConfig(config)
if err != nil {
rErr = errors.Wrap(err, "init kms v3 client with config failed")
} else {
if len(strings.TrimSpace(caVerify)) != 0 {
logger.Debugf("set kms client Ca with content: %s\n", caVerify[:len(caVerify)/maskUnit32Width])
kmsClient.SetVerify(caVerify)
} else {
kmsClient.SetHTTPSInsecure(true)
}
kmsClient.setKmsVersion(constant.KMSv3)
}
return kmsClient, rErr
}
func checkKmsV3InitParam(config *dkms_api.Config, caVerify string) error {
if len(*config.Endpoint) == 0 {
return EmptyEndpointKmsV3ClientInitError
}
if len(*config.Password) == 0 {
return EmptyPasswordKmsV3ClientInitError
}
if len(*config.ClientKeyContent) == 0 {
return EmptyClientKeyContentKmsV3ClientInitError
}
if len(caVerify) == 0 {
return EmptyCaVerifyKmsV3ClientInitError
}
return nil
}
func newKmsV1ClientWithAccessKey(regionId, ak, sk string) (*TransferKmsClient, error) {
logger.Debugf("init kms client with region:[%s], ak:[%s]xxx, sk:[%s]xxx\n",
regionId, ak[:len(ak)/maskUnit8Width], sk[:len(sk)/maskUnit8Width])
return newKmsClient(regionId, ak, sk, nil)
}
func newKmsV3ClientWithConfig(config *dkms_api.Config) (*TransferKmsClient, error) {
logger.Debugf("init kms client with endpoint:[%s], clientKeyContent:[%s], password:[%s]\n",
config.Endpoint, (*config.ClientKeyContent)[:len(*config.ClientKeyContent)/maskUnit8Width],
(*config.Password)[:len(*config.Password)/maskUnit8Width])
return newKmsClient("", "", "", config)
}
func newKmsClient(regionId, ak, sk string, config *dkms_api.Config) (*TransferKmsClient, error) {
client, err := dkms_transfer.NewClientWithAccessKey(regionId, ak, sk, config)
if err != nil {
return nil, err
}
return &TransferKmsClient{
KmsTransferClient: client,
}, nil
}
func (kmsClient *TransferKmsClient) GetKmsVersion() constant.KMSVersion {
return kmsClient.kmsVersion
}
func (kmsClient *TransferKmsClient) setKmsVersion(kmsVersion constant.KMSVersion) {
logger.Debug("successfully set kms client version to " + kmsVersion)
kmsClient.kmsVersion = kmsVersion
}
func (kmsClient *TransferKmsClient) GenerateDataKey(keyId, keySpec string) (string, string, error) {
generateDataKeyRequest := kms.CreateGenerateDataKeyRequest()
generateDataKeyRequest.Scheme = kmsScheme
generateDataKeyRequest.AcceptFormat = kmsAcceptFormat
generateDataKeyRequest.KeyId = keyId
generateDataKeyRequest.KeySpec = keySpec
generateDataKeyResponse, err := kmsClient.KmsTransferClient.GenerateDataKey(generateDataKeyRequest)
if err != nil {
return "", "", err
}
return generateDataKeyResponse.Plaintext, generateDataKeyResponse.CiphertextBlob, nil
}
func (kmsClient *TransferKmsClient) Decrypt(cipherContent string) (string, error) {
request := kms.CreateDecryptRequest()
request.Method = http.MethodPost
request.Scheme = kmsScheme
request.AcceptFormat = kmsAcceptFormat
request.CiphertextBlob = cipherContent
response, err := kmsClient.KmsTransferClient.Decrypt(request)
if err != nil {
return "", fmt.Errorf("kms decrypt failed: %v", err)
}
return response.Plaintext, nil
}
func (kmsClient *TransferKmsClient) Encrypt(content, keyId string) (string, error) {
request := kms.CreateEncryptRequest()
request.Method = http.MethodPost
request.Scheme = kmsScheme
request.AcceptFormat = kmsAcceptFormat
request.Plaintext = content
request.KeyId = keyId
response, err := kmsClient.KmsTransferClient.Encrypt(request)
if err != nil {
return "", fmt.Errorf("kms encrypt failed: %v", err)
}
return response.CiphertextBlob, nil
}
func GetDefaultKMSv1KeyId() string {
return constant.MSE_KMSv1_DEFAULT_KEY_ID
}
type RamKmsClient struct {
*kms20160120.Client
kmsVersion constant.KMSVersion
runtime *util.RuntimeOptions
}
func NewKmsRamClient(kmsConfig *constant.KMSConfig, regionId, ak, sk string) (*RamKmsClient, error) {
if kmsConfig == nil || len(kmsConfig.Endpoint) == 0 {
if err := checkKmsV1InitParam(regionId, ak, sk); err != nil {
return nil, err
}
KmsV1Config := &openapi.Config{}
KmsV1Config.AccessKeyId = tea.String(ak)
KmsV1Config.AccessKeySecret = tea.String(sk)
KmsV1Config.RegionId = tea.String(regionId)
_result, _err := kms20160120.NewClient(KmsV1Config)
if _err != nil {
return nil, _err
}
_ramClient := &RamKmsClient{
Client: _result,
kmsVersion: constant.KMSv1,
runtime: &util.RuntimeOptions{},
}
return _ramClient, nil
}
if err := checkKmsRamInitParam(kmsConfig.Endpoint, ak, sk); err != nil {
return nil, err
}
config := &openapi.Config{}
config.AccessKeyId = tea.String(ak)
config.AccessKeySecret = tea.String(sk)
if len(regionId) != 0 {
config.RegionId = tea.String(regionId)
}
config.Endpoint = tea.String(kmsConfig.Endpoint)
config.Ca = tea.String(kmsConfig.CaContent)
runtimeOption := &util.RuntimeOptions{}
if len(kmsConfig.CaContent) == 0 {
runtimeOption.IgnoreSSL = tea.Bool(true)
}
if kmsConfig.OpenSSL == "true" {
runtimeOption.IgnoreSSL = tea.Bool(false)
} else if kmsConfig.OpenSSL == "false" {
runtimeOption.IgnoreSSL = tea.Bool(true)
}
_result, _err := kms20160120.NewClient(config)
if _err != nil {
return nil, _err
}
_ramClient := &RamKmsClient{
Client: _result,
kmsVersion: constant.KMSv3,
runtime: runtimeOption,
}
return _ramClient, nil
}
func (kmsClient *RamKmsClient) GetKmsVersion() constant.KMSVersion {
return kmsClient.kmsVersion
}
func (kmsClient *RamKmsClient) setKmsVersion(kmsVersion constant.KMSVersion) {
logger.Debug("successfully set kms client version to " + kmsVersion)
kmsClient.kmsVersion = kmsVersion
}
func (kmsClient *RamKmsClient) GenerateDataKey(keyId, keySpec string) (string, string, error) {
request := &kms20160120.GenerateDataKeyRequest{
KeyId: tea.String(keyId),
KeySpec: tea.String(keySpec),
}
_body, _err := kmsClient.Client.GenerateDataKeyWithOptions(request, kmsClient.runtime)
if _err != nil {
return "", "", _err
}
return *_body.Body.Plaintext, *_body.Body.CiphertextBlob, nil
}
func (kmsClient *RamKmsClient) Decrypt(cipherContent string) (string, error) {
request := &kms20160120.DecryptRequest{
CiphertextBlob: tea.String(cipherContent),
}
_body, _err := kmsClient.Client.DecryptWithOptions(request, kmsClient.runtime)
if _err != nil {
return "", _err
}
return *_body.Body.Plaintext, nil
}
func (kmsClient *RamKmsClient) Encrypt(content, keyId string) (string, error) {
request := &kms20160120.EncryptRequest{
Plaintext: tea.String(content),
KeyId: tea.String(keyId),
}
_body, _err := kmsClient.Client.EncryptWithOptions(request, kmsClient.runtime)
if _err != nil {
return "", _err
}
return *_body.Body.CiphertextBlob, nil
}

View File

@ -0,0 +1,299 @@
/*
* Copyright 1999-2020 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.
*/
package encryption
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
inner_encoding "github.com/nacos-group/nacos-sdk-go/v2/common/encoding"
"strings"
)
type kmsPlugin struct {
kmsClient KmsClient
}
func (k *kmsPlugin) Encrypt(param *HandlerParam) error {
err := k.encryptionParamCheck(*param)
if err != nil {
return err
}
secretKeyBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.PlainDataKey))
if err != nil {
return err
}
contentUtf8Bytes := inner_encoding.DecodeString2Utf8Bytes(param.Content)
encryptedContent, err := AesEcbPkcs5PaddingEncrypt(contentUtf8Bytes, secretKeyBase64Decoded)
if err != nil {
return err
}
contentBase64Encoded, err := inner_encoding.EncodeBase64(encryptedContent)
if err != nil {
return err
}
param.Content = inner_encoding.EncodeUtf8Bytes2String(contentBase64Encoded)
return nil
}
func (k *kmsPlugin) Decrypt(param *HandlerParam) error {
err := k.decryptionParamCheck(*param)
if err != nil {
return err
}
secretKeyBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.PlainDataKey))
if err != nil {
return err
}
contentBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.Content))
if err != nil {
return err
}
decryptedContent, err := AesEcbPkcs5PaddingDecrypt(contentBase64Decoded, secretKeyBase64Decoded)
if err != nil {
return err
}
param.Content = inner_encoding.EncodeUtf8Bytes2String(decryptedContent)
return nil
}
func (k *kmsPlugin) AlgorithmName() string {
return ""
}
func (k *kmsPlugin) GenerateSecretKey(param *HandlerParam) (string, error) {
return "", nil
}
func (k *kmsPlugin) EncryptSecretKey(param *HandlerParam) (string, error) {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return "", err
}
if len(param.PlainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
encryptedDataKey, err := k.kmsClient.Encrypt(param.PlainDataKey, keyId)
if err != nil {
return "", err
}
if len(encryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
param.EncryptedDataKey = encryptedDataKey
return encryptedDataKey, nil
}
func (k *kmsPlugin) DecryptSecretKey(param *HandlerParam) (string, error) {
if len(param.EncryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
plainDataKey, err := k.kmsClient.Decrypt(param.EncryptedDataKey)
if err != nil {
return "", err
}
if len(plainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
param.PlainDataKey = plainDataKey
return plainDataKey, nil
}
func (k *kmsPlugin) encryptionParamCheck(param HandlerParam) error {
if err := k.plainDataKeyParamCheck(param.PlainDataKey); err != nil {
return KeyIdParamCheckError
}
if err := k.contentParamCheck(param.Content); err != nil {
return ContentParamCheckError
}
return nil
}
func (k *kmsPlugin) decryptionParamCheck(param HandlerParam) error {
return k.encryptionParamCheck(param)
}
func (k *kmsPlugin) plainDataKeyParamCheck(plainDataKey string) error {
if len(plainDataKey) == 0 {
return EmptyPlainDataKeyError
}
return nil
}
func (k *kmsPlugin) dataIdParamCheck(dataId string) error {
if !strings.Contains(dataId, CipherPrefix) {
return fmt.Errorf("dataId prefix should start with: %s", CipherPrefix)
}
return nil
}
func (k *kmsPlugin) keyIdParamCheck(keyId string) (string, error) {
if len(strings.TrimSpace(keyId)) == 0 {
if k.kmsClient.GetKmsVersion() == constant.KMSv1 {
return GetDefaultKMSv1KeyId(), nil
}
return "", KeyIdParamCheckError
}
return keyId, nil
}
func (k *kmsPlugin) contentParamCheck(content string) error {
if len(content) == 0 {
return fmt.Errorf("content need to encrypt is nil")
}
return nil
}
type KmsAes128Plugin struct {
kmsPlugin
}
func (k *KmsAes128Plugin) Encrypt(param *HandlerParam) error {
return k.kmsPlugin.Encrypt(param)
}
func (k *KmsAes128Plugin) Decrypt(param *HandlerParam) error {
return k.kmsPlugin.Decrypt(param)
}
func (k *KmsAes128Plugin) AlgorithmName() string {
return KmsAes128AlgorithmName
}
func (k *KmsAes128Plugin) GenerateSecretKey(param *HandlerParam) (string, error) {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return "", err
}
plainSecretKey, encryptedSecretKey, err := k.kmsClient.GenerateDataKey(keyId, kmsAes128KeySpec)
if err != nil {
return "", err
}
param.PlainDataKey = plainSecretKey
param.EncryptedDataKey = encryptedSecretKey
if len(param.PlainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
if len(param.EncryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
return plainSecretKey, nil
}
func (k *KmsAes128Plugin) EncryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.EncryptSecretKey(param)
}
func (k *KmsAes128Plugin) DecryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.DecryptSecretKey(param)
}
type KmsAes256Plugin struct {
kmsPlugin
}
func (k *KmsAes256Plugin) Encrypt(param *HandlerParam) error {
return k.kmsPlugin.Encrypt(param)
}
func (k *KmsAes256Plugin) Decrypt(param *HandlerParam) error {
return k.kmsPlugin.Decrypt(param)
}
func (k *KmsAes256Plugin) AlgorithmName() string {
return KmsAes256AlgorithmName
}
func (k *KmsAes256Plugin) GenerateSecretKey(param *HandlerParam) (string, error) {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return "", err
}
plainSecretKey, encryptedSecretKey, err := k.kmsClient.GenerateDataKey(keyId, kmsAes256KeySpec)
if err != nil {
return "", err
}
param.PlainDataKey = plainSecretKey
param.EncryptedDataKey = encryptedSecretKey
if len(param.PlainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
if len(param.EncryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
return plainSecretKey, nil
}
func (k *KmsAes256Plugin) EncryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.EncryptSecretKey(param)
}
func (k *KmsAes256Plugin) DecryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.DecryptSecretKey(param)
}
type KmsBasePlugin struct {
kmsPlugin
}
func (k *KmsBasePlugin) Encrypt(param *HandlerParam) error {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return err
}
if len(param.Content) == 0 {
return EmptyContentError
}
encryptedContent, err := k.kmsClient.Encrypt(param.Content, keyId)
if err != nil {
return err
}
param.Content = encryptedContent
return nil
}
func (k *KmsBasePlugin) Decrypt(param *HandlerParam) error {
if len(param.Content) == 0 {
return nil
}
plainContent, err := k.kmsClient.Decrypt(param.Content)
if err != nil {
return err
}
param.Content = plainContent
return nil
}
func (k *KmsBasePlugin) AlgorithmName() string {
return KmsAlgorithmName
}
func (k *KmsBasePlugin) GenerateSecretKey(param *HandlerParam) (string, error) {
return "", nil
}
func (k *KmsBasePlugin) EncryptSecretKey(param *HandlerParam) (string, error) {
return "", nil
}
func (k *KmsBasePlugin) DecryptSecretKey(param *HandlerParam) (string, error) {
return "", nil
}

89
common/file/file.go Normal file
View File

@ -0,0 +1,89 @@
/*
* Copyright 1999-2020 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.
*/
package file
import (
"log"
"os"
"path/filepath"
"runtime"
"strings"
)
var osType string
var path string
const WINDOWS = "windows"
func init() {
osType = runtime.GOOS
if os.IsPathSeparator('\\') { //前边的判断是否是系统的分隔符
path = "\\"
} else {
path = "/"
}
}
func MkdirIfNecessary(createDir string) (err error) {
s := strings.Split(createDir, path)
startIndex := 0
dir := ""
if s[0] == "" {
startIndex = 1
} else {
dir, _ = os.Getwd() //当前的目录
}
for i := startIndex; i < len(s); i++ {
var d string
if osType == WINDOWS && filepath.IsAbs(createDir) {
d = strings.Join(s[startIndex:i+1], path)
} else {
d = dir + path + strings.Join(s[startIndex:i+1], path)
}
if _, e := os.Stat(d); os.IsNotExist(e) {
err = os.Mkdir(d, os.ModePerm) //在当前目录下生成md目录
if err != nil {
break
}
}
}
return err
}
func GetCurrentPath() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Println("can not get current path")
}
return dir
}
func IsExistFile(filePath string) bool {
if len(filePath) == 0 {
return false
}
_, err := os.Stat(filePath)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}

30
common/file/file_test.go Normal file
View File

@ -0,0 +1,30 @@
/*
* Copyright 1999-2020 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.
*/
package file
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMkdirIfNecessaryForAbsPath(t *testing.T) {
path := GetCurrentPath() + string(os.PathSeparator) + "log"
err := MkdirIfNecessary(path)
assert.Nil(t, err)
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 1999-2020 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.
*/
package filter
import (
nacos_inner_encryption "github.com/nacos-group/nacos-sdk-go/v2/common/encryption"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/pkg/errors"
"strings"
)
const (
defaultConfigEncryptionFilterName = "defaultConfigEncryptionFilter"
)
var (
noNeedEncryptionError = errors.New("dataId doesn't need to encrypt/decrypt.")
)
type DefaultConfigEncryptionFilter struct {
handler nacos_inner_encryption.Handler
}
func NewDefaultConfigEncryptionFilter(handler nacos_inner_encryption.Handler) IConfigFilter {
return &DefaultConfigEncryptionFilter{handler}
}
func (d *DefaultConfigEncryptionFilter) DoFilter(param *vo.ConfigParam) error {
if err := d.paramCheck(*param); err != nil {
if errors.Is(err, noNeedEncryptionError) {
return nil
}
}
if param.UsageType == vo.RequestType {
encryptionParam := &nacos_inner_encryption.HandlerParam{
DataId: param.DataId,
Content: param.Content,
KeyId: param.KmsKeyId,
}
if err := d.handler.EncryptionHandler(encryptionParam); err != nil {
return err
}
param.Content = encryptionParam.Content
param.EncryptedDataKey = encryptionParam.EncryptedDataKey
} else if param.UsageType == vo.ResponseType {
decryptionParam := &nacos_inner_encryption.HandlerParam{
DataId: param.DataId,
Content: param.Content,
EncryptedDataKey: param.EncryptedDataKey,
}
if err := d.handler.DecryptionHandler(decryptionParam); err != nil {
return err
}
param.Content = decryptionParam.Content
}
return nil
}
func (d *DefaultConfigEncryptionFilter) GetOrder() int {
return 0
}
func (d *DefaultConfigEncryptionFilter) GetFilterName() string {
return defaultConfigEncryptionFilterName
}
func (d *DefaultConfigEncryptionFilter) paramCheck(param vo.ConfigParam) error {
if !strings.HasPrefix(param.DataId, nacos_inner_encryption.CipherPrefix) ||
len(strings.TrimSpace(param.Content)) == 0 {
return noNeedEncryptionError
}
return nil
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 1999-2020 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.
*/
package filter
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
type IConfigFilterChain interface {
AddFilter(IConfigFilter) error
GetFilters() []IConfigFilter
DoFilters(*vo.ConfigParam) error
DoFilterByName(*vo.ConfigParam, string) error
}
type IConfigFilter interface {
DoFilter(*vo.ConfigParam) error
GetOrder() int
GetFilterName() string
}
func RegisterConfigFilterToChain(chain IConfigFilterChain, filter IConfigFilter) error {
return chain.AddFilter(filter)
}
func NewConfigFilterChainManager() IConfigFilterChain {
return newConfigFilterChainManager()
}
func newConfigFilterChainManager() *DefaultConfigFilterChainManager {
return &DefaultConfigFilterChainManager{
configFilterPriorityQueue: make([]IConfigFilter, 0, 2),
}
}
type DefaultConfigFilterChainManager struct {
configFilterPriorityQueue
}
func (m *DefaultConfigFilterChainManager) AddFilter(filter IConfigFilter) error {
return m.configFilterPriorityQueue.addFilter(filter)
}
func (m *DefaultConfigFilterChainManager) GetFilters() []IConfigFilter {
return m.configFilterPriorityQueue
}
func (m *DefaultConfigFilterChainManager) DoFilters(param *vo.ConfigParam) error {
for index := 0; index < len(m.GetFilters()); index++ {
if err := m.GetFilters()[index].DoFilter(param); err != nil {
return err
}
}
return nil
}
func (m *DefaultConfigFilterChainManager) DoFilterByName(param *vo.ConfigParam, name string) error {
for index := 0; index < len(m.GetFilters()); index++ {
if m.GetFilters()[index].GetFilterName() == name {
if err := m.GetFilters()[index].DoFilter(param); err != nil {
return err
}
return nil
}
}
return fmt.Errorf("cannot find the filter[%s]", name)
}
type configFilterPriorityQueue []IConfigFilter
func (c *configFilterPriorityQueue) addFilter(filter IConfigFilter) error {
var pos int = len(*c)
for i := 0; i < len(*c); i++ {
if filter.GetFilterName() == (*c)[i].GetFilterName() {
return nil
}
if filter.GetOrder() < (*c)[i].GetOrder() {
pos = i
break
}
}
if pos == len(*c) {
*c = append((*c)[:], filter)
} else {
temp := append((*c)[:pos], filter)
*c = append(temp[:], (*c)[pos:]...)
}
return nil
}

View File

@ -1,31 +1,40 @@
/*
* Copyright 1999-2020 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.
*/
package http_agent
import (
"net/http"
"net/url"
"strings"
"time"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-08 14:08
**/
func delete(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.HasSuffix(path, "?") {
path = path + "?"
func delete(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if len(params) > 0 {
if !strings.HasSuffix(path, "?") {
path = path + "?"
}
for key, value := range params {
path = path + key + "=" + url.QueryEscape(value) + "&"
}
if strings.HasSuffix(path, "&") {
path = path[:len(path)-1]
}
}
for key, value := range params {
path = path + key + "=" + value + "&"
}
if strings.HasSuffix(path, "&") {
path = path[:len(path)-1]
}
client := http.Client{}
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
request, errNew := http.NewRequest(http.MethodDelete, path, nil)
if errNew != nil {

View File

@ -1,3 +1,19 @@
/*
* Copyright 1999-2020 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.
*/
package http_agent
import (
@ -7,14 +23,6 @@ import (
"strconv"
)
/**
*
* @description : mock http response
*
* @author : codezhang
*
* @create : 2019-01-11 12:10
**/
type fakeHttpResponseBody struct {
body io.ReadSeeker
}

View File

@ -1,33 +1,43 @@
/*
* Copyright 1999-2020 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.
*/
package http_agent
import (
"net/http"
"net/url"
"strings"
"time"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-07 15:13
**/
func get(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.HasSuffix(path, "?") {
func get(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.Contains(path, "?") {
path = path + "?"
}
for key, value := range params {
path = path + key + "=" + value + "&"
if !strings.HasSuffix(path, "&") {
path = path + "&"
}
path = path + key + "=" + url.QueryEscape(value) + "&"
}
if strings.HasSuffix(path, "&") {
path = path[:len(path)-1]
}
client := http.Client{}
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
request, errNew := http.NewRequest(http.MethodGet, path, nil)
if errNew != nil {

View File

@ -1,27 +1,44 @@
/*
* Copyright 1999-2020 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.
*/
package http_agent
import (
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/utils"
"io/ioutil"
"log"
"io"
"net/http"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/tls"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-10 11:26
**/
type HttpAgent struct {
TlsConfig constant.TLSConfig
}
func (agent *HttpAgent) Get(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return get(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return get(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) RequestOnlyResult(method string, path string, header http.Header, timeoutMs uint64, params map[string]string) string {
@ -41,20 +58,20 @@ func (agent *HttpAgent) RequestOnlyResult(method string, path string, header htt
response, err = agent.Delete(path, header, timeoutMs, params)
break
default:
log.Printf("[ERROR]:request method[%s], path[%s],header:[%s],params:[%s], not avaliable method ", method, path, utils.ToJsonString(header), utils.ToJsonString(params))
logger.Errorf("request method[%s], path[%s],header:[%s],params:[%s], not avaliable method ", method, path, util.ToJsonString(header), util.ToJsonString(params))
}
if err != nil {
log.Printf("[ERROR]:request method[%s],request path[%s],header:[%s],params:[%s],err:%s", method, path, utils.ToJsonString(header), utils.ToJsonString(params), err.Error())
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],err:%+v", method, path, util.ToJsonString(header), util.ToJsonString(params), err)
return ""
}
if response.StatusCode != 200 {
log.Printf("[ERROR]:request method[%s],request path[%s],header:[%s],params:[%s],status code error:%d", method, path, utils.ToJsonString(header), utils.ToJsonString(params), response.StatusCode)
if response.StatusCode != constant.RESPONSE_CODE_SUCCESS {
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],status code error:%d", method, path, util.ToJsonString(header), util.ToJsonString(params), response.StatusCode)
return ""
}
bytes, errRead := ioutil.ReadAll(response.Body)
bytes, errRead := io.ReadAll(response.Body)
defer response.Body.Close()
if errRead != nil {
log.Printf("[ERROR]:request method[%s],request path[%s],header:[%s],params:[%s],read error:%s", method, path, utils.ToJsonString(header), utils.ToJsonString(params), errRead.Error())
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],read error:%+v", method, path, util.ToJsonString(header), util.ToJsonString(params), errRead)
return ""
}
return string(bytes)
@ -76,20 +93,44 @@ func (agent *HttpAgent) Request(method string, path string, header http.Header,
response, err = agent.Delete(path, header, timeoutMs, params)
return
default:
err = errors.New("not avaliable method")
log.Printf("[ERROR]:request method[%s], path[%s],header:[%s],params:[%s], not avaliable method ", method, path, utils.ToJsonString(header), utils.ToJsonString(params))
err = errors.New("not available method")
logger.Errorf("request method[%s], path[%s],header:[%s],params:[%s], not available method ", method, path, util.ToJsonString(header), util.ToJsonString(params))
}
return
}
func (agent *HttpAgent) Post(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return post(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return post(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) Delete(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return delete(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return delete(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) Put(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return put(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return put(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) createClient() (*http.Client, error) {
if !agent.TlsConfig.Enable {
return &http.Client{}, nil
}
cfg, err := tls.NewTLS(agent.TlsConfig)
if err != nil {
return nil, err
}
return &http.Client{Transport: &http.Transport{TLSClientConfig: cfg}}, nil
}

View File

@ -1,16 +1,23 @@
/*
* Copyright 1999-2020 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.
*/
package http_agent
import "net/http"
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-10 11:07
**/
//go:generate mockgen -destination ../../mock/mock_http_agent_interface.go -package mock -source=./http_agent_interface.go
type IHttpAgent interface {

View File

@ -1,32 +1,32 @@
/*
* Copyright 1999-2020 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.
*/
package http_agent
import (
"net/http"
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-07 15:13
**/
func post(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client := http.Client{}
func post(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
var body string
for key, value := range params {
if len(value) > 0 {
body += key + "=" + value + "&"
}
}
if strings.HasSuffix(body, "&") {
body = body[:len(body)-1]
}
body := util.GetUrlFormedMap(params)
request, errNew := http.NewRequest(http.MethodPost, path, strings.NewReader(body))
if errNew != nil {
err = errNew

View File

@ -1,23 +1,28 @@
/*
* Copyright 1999-2020 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.
*/
package http_agent
import (
"log"
"net/http"
"strings"
"time"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 11:24
**/
func put(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client := http.Client{}
func put(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
var body string
for key, value := range params {
@ -36,7 +41,6 @@ func put(path string, header http.Header, timeoutMs uint64, params map[string]st
request.Header = header
resp, errDo := client.Do(request)
if errDo != nil {
log.Println(errDo)
err = errDo
} else {
response = resp

View File

@ -1,25 +1,182 @@
/*
* Copyright 1999-2020 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.
*/
package logger
import (
"github.com/lestrrat/go-file-rotatelogs"
"github.com/nacos-group/nacos-sdk-go/common/util"
"log"
"os"
"path/filepath"
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
func InitLog(logDir string) error {
err := util.MkdirIfNecessary(logDir)
if err != nil {
return err
}
logDir = logDir + string(os.PathSeparator)
rl, err := rotatelogs.New(filepath.Join(logDir, "nacos-sdk.log-%Y%m%d%H%M"), rotatelogs.WithRotationTime(time.Hour), rotatelogs.WithMaxAge(48*time.Hour), rotatelogs.WithLinkName(filepath.Join(logDir, "nacos-sdk.log")))
if err != nil {
return err
}
log.SetOutput(rl)
log.SetFlags(log.LstdFlags)
return nil
var (
logger Logger
logLock sync.RWMutex
)
var levelMap = map[string]zapcore.Level{
"debug": zapcore.DebugLevel,
"info": zapcore.InfoLevel,
"warn": zapcore.WarnLevel,
"error": zapcore.ErrorLevel,
}
type Config struct {
Level string
Sampling *SamplingConfig
AppendToStdout bool
LogRollingConfig *lumberjack.Logger
}
type SamplingConfig struct {
Initial int
Thereafter int
Tick time.Duration
}
type NacosLogger struct {
Logger
}
// Logger is the interface for Logger types
type Logger interface {
Info(args ...interface{})
Warn(args ...interface{})
Error(args ...interface{})
Debug(args ...interface{})
Infof(fmt string, args ...interface{})
Warnf(fmt string, args ...interface{})
Errorf(fmt string, args ...interface{})
Debugf(fmt string, args ...interface{})
}
func init() {
zapLoggerConfig := zap.NewDevelopmentConfig()
zapLoggerEncoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
zapLoggerConfig.EncoderConfig = zapLoggerEncoderConfig
zapLogger, _ := zapLoggerConfig.Build(zap.AddCaller(), zap.AddCallerSkip(1))
SetLogger(&NacosLogger{zapLogger.Sugar()})
}
func BuildLoggerConfig(clientConfig constant.ClientConfig) Config {
loggerConfig := Config{
Level: clientConfig.LogLevel,
AppendToStdout: clientConfig.AppendToStdout,
}
if clientConfig.LogSampling != nil {
loggerConfig.Sampling = &SamplingConfig{
Initial: clientConfig.LogSampling.Initial,
Thereafter: clientConfig.LogSampling.Thereafter,
Tick: clientConfig.LogSampling.Tick,
}
}
loggerConfig.LogRollingConfig = &lumberjack.Logger{
Filename: clientConfig.LogDir + string(os.PathSeparator) + constant.LOG_FILE_NAME,
}
logRollingConfig := clientConfig.LogRollingConfig
if logRollingConfig != nil {
loggerConfig.LogRollingConfig.MaxSize = logRollingConfig.MaxSize
loggerConfig.LogRollingConfig.MaxAge = logRollingConfig.MaxAge
loggerConfig.LogRollingConfig.MaxBackups = logRollingConfig.MaxBackups
loggerConfig.LogRollingConfig.LocalTime = logRollingConfig.LocalTime
loggerConfig.LogRollingConfig.Compress = logRollingConfig.Compress
} else {
loggerConfig.LogRollingConfig.MaxSize = 100
loggerConfig.LogRollingConfig.MaxAge = 30
loggerConfig.LogRollingConfig.MaxBackups = 5
loggerConfig.LogRollingConfig.LocalTime = true
loggerConfig.LogRollingConfig.Compress = false
}
return loggerConfig
}
// InitLogger is init global logger for nacos
func InitLogger(config Config) (err error) {
logLock.Lock()
defer logLock.Unlock()
logger, err = InitNacosLogger(config)
return
}
// InitNacosLogger is init nacos default logger
func InitNacosLogger(config Config) (Logger, error) {
logLevel := getLogLevel(config.Level)
encoder := getEncoder()
writer := config.getLogWriter()
if config.AppendToStdout {
writer = zapcore.NewMultiWriteSyncer(writer, zapcore.AddSync(os.Stdout))
}
core := zapcore.NewCore(zapcore.NewConsoleEncoder(encoder), writer, logLevel)
zaplogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
return &NacosLogger{zaplogger.Sugar()}, nil
}
func getLogLevel(level string) zapcore.Level {
if zapLevel, ok := levelMap[level]; ok {
return zapLevel
}
return zapcore.InfoLevel
}
func getEncoder() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
// SetLogger sets logger for sdk
func SetLogger(log Logger) {
logLock.Lock()
defer logLock.Unlock()
logger = log
}
func GetLogger() Logger {
logLock.RLock()
defer logLock.RUnlock()
return logger
}
// getLogWriter get Lumberjack writer by LumberjackConfig
func (c *Config) getLogWriter() zapcore.WriteSyncer {
return zapcore.AddSync(c.LogRollingConfig)
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 1999-2020 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.
*/
package logger
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func reset() {
SetLogger(nil)
}
func TestInitLogger(t *testing.T) {
config := Config{
Level: "degug",
}
err := InitLogger(config)
assert.NoError(t, err)
reset()
}
func TestGetLogger(t *testing.T) {
// not yet init get default log
log := GetLogger()
config := Config{
Level: "debug",
}
_ = InitLogger(config)
// after init logger
log2 := GetLogger()
assert.NotEqual(t, log, log2)
reset()
}
func TestSetLogger(t *testing.T) {
// not yet init get default log
log := GetLogger()
log1 := &mockLogger{}
SetLogger(log1)
// after set logger
log2 := GetLogger()
assert.NotEqual(t, log, log2)
assert.Equal(t, log1, log2)
reset()
}
func TestRaceLogger(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(3)
go func() {
defer wg.Done()
SetLogger(&mockLogger{})
}()
go func() {
defer wg.Done()
_ = GetLogger()
}()
go func() {
defer wg.Done()
config := Config{
Level: "degug",
}
_ = InitLogger(config)
}()
}
wg.Wait()
reset()
}
type mockLogger struct {
}
func (m mockLogger) Info(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Warn(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Error(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Debug(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Infof(fmt string, args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Warnf(fmt string, args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Errorf(fmt string, args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Debugf(fmt string, args ...interface{}) {
panic("implement me")
}

57
common/logger/logging.go Normal file
View File

@ -0,0 +1,57 @@
/*
* Copyright 1999-2020 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.
*/
package logger
// Info is info level
func Info(args ...interface{}) {
GetLogger().Info(args...)
}
// Warn is warning level
func Warn(args ...interface{}) {
GetLogger().Warn(args...)
}
// Error is error level
func Error(args ...interface{}) {
GetLogger().Error(args...)
}
// Debug is debug level
func Debug(args ...interface{}) {
GetLogger().Debug(args...)
}
// Infof is format info level
func Infof(fmt string, args ...interface{}) {
GetLogger().Infof(fmt, args...)
}
// Warnf is format warning level
func Warnf(fmt string, args ...interface{}) {
GetLogger().Warnf(fmt, args...)
}
// Errorf is format error level
func Errorf(fmt string, args ...interface{}) {
GetLogger().Errorf(fmt, args...)
}
// Debugf is format debug level
func Debugf(fmt string, args ...interface{}) {
GetLogger().Debugf(fmt, args...)
}

65
common/monitor/monitor.go Normal file
View File

@ -0,0 +1,65 @@
/*
* Copyright 1999-2020 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.
*/
package monitor
import "github.com/prometheus/client_golang/prometheus"
var (
gaugeMonitorVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "nacos_monitor",
Help: "nacos_monitor",
}, []string{"module", "name"})
histogramMonitorVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "nacos_client_request",
Help: "nacos_client_request",
}, []string{"module", "method", "url", "code"})
)
// register collectors vec
func init() {
prometheus.MustRegister(gaugeMonitorVec, histogramMonitorVec)
}
// get gauge with labels and use gaugeMonitorVec
func GetGaugeWithLabels(labels ...string) prometheus.Gauge {
return gaugeMonitorVec.WithLabelValues(labels...)
}
func GetServiceInfoMapSizeMonitor() prometheus.Gauge {
return GetGaugeWithLabels("serviceInfo", "serviceInfoMapSize")
}
func GetDom2BeatSizeMonitor() prometheus.Gauge {
return GetGaugeWithLabels("dom2Beat", "dom2BeatSize")
}
func GetListenConfigCountMonitor() prometheus.Gauge {
return GetGaugeWithLabels("listenConfig", "listenConfigCount")
}
// get histogram with labels and use histogramMonitorVec
func GetHistogramWithLabels(labels ...string) prometheus.Observer {
return histogramMonitorVec.WithLabelValues(labels...)
}
func GetConfigRequestMonitor(method, url, code string) prometheus.Observer {
return GetHistogramWithLabels("config", method, url, code)
}
func GetNamingRequestMonitor(method, url, code string) prometheus.Observer {
return GetHistogramWithLabels("naming", method, url, code)
}

View File

@ -0,0 +1,39 @@
package monitor
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGaugeMonitor(t *testing.T) {
t.Run("getGaugeWithLabels", func(t *testing.T) {
// will panic because of wrong label count.
defer func() {
r := recover()
assert.NotNil(t, r)
}()
GetGaugeWithLabels("gauge", "test_gauge", "should_not_exist_label")
})
t.Run("serviceInfoMapMonitor", func(t *testing.T) {
monitor := GetServiceInfoMapSizeMonitor()
assert.NotNil(t, monitor)
})
}
func TestHistorgam(t *testing.T) {
t.Run("getHistogram", func(t *testing.T) {
// will panic because of wrong label count.
defer func() {
r := recover()
assert.NotNil(t, r)
}()
GetHistogramWithLabels("histogram", "test_histogram", "should_not_exist_label")
})
t.Run("serviceInfoMapMonitor", func(t *testing.T) {
monitor := GetConfigRequestMonitor("GET", "url", "NA")
assert.NotNil(t, monitor)
})
}

View File

@ -1,18 +1,26 @@
/*
* Copyright 1999-2020 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.
*/
package nacos_error
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/common/constant"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-14 11:22
**/
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
type NacosError struct {
errorCode string

View File

@ -1,86 +1,142 @@
/*
* Copyright 1999-2020 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.
*/
package nacos_server
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/satori/go.uuid"
"io/ioutil"
"log"
"context"
"io"
"math/rand"
"net/http"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type NacosServer struct {
sync.RWMutex
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
endpoint string
lastSrvRefTime int64
vipSrvRefInterMills int64
securityLogin security.SecurityProxy
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
endpoint string
lastSrvRefTime int64
vipSrvRefInterMills int64
contextPath string
endpointContextPath string
endpointQueryParams string
endpointQueryHeader map[string][]string
clusterName string
currentIndex int32
ServerSrcChangeSignal chan struct{}
}
func NewNacosServer(serverList []constant.ServerConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string) (NacosServer, error) {
if len(serverList) == 0 && endpoint == "" {
return NacosServer{}, errors.New("both serverlist and endpoint are empty")
func NewNacosServer(ctx context.Context, serverList []constant.ServerConfig, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string, endpointQueryHeader map[string][]string) (*NacosServer, error) {
return NewNacosServerWithRamCredentialProvider(ctx, serverList, clientCfg, httpAgent, timeoutMs, endpoint, endpointQueryHeader, nil)
}
func NewNacosServerWithRamCredentialProvider(ctx context.Context, serverList []constant.ServerConfig, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string, endpointQueryHeader map[string][]string, provider security.RamCredentialProvider) (*NacosServer, error) {
severLen := len(serverList)
if severLen == 0 && endpoint == "" {
return &NacosServer{}, errors.New("both serverlist and endpoint are empty")
}
securityLogin := security.NewSecurityProxyWithRamCredentialProvider(clientCfg, serverList, httpAgent, provider)
ns := NacosServer{
serverList: serverList,
httpAgent: httpAgent,
timeoutMs: timeoutMs,
endpoint: endpoint,
vipSrvRefInterMills: 10000,
serverList: serverList,
securityLogin: securityLogin,
httpAgent: httpAgent,
timeoutMs: timeoutMs,
endpoint: endpoint,
vipSrvRefInterMills: 10000,
endpointContextPath: clientCfg.EndpointContextPath,
endpointQueryParams: clientCfg.EndpointQueryParams,
endpointQueryHeader: endpointQueryHeader,
clusterName: clientCfg.ClusterName,
contextPath: clientCfg.ContextPath,
ServerSrcChangeSignal: make(chan struct{}, 1),
}
ns.initRefreshSrvIfNeed()
return ns, nil
if severLen > 0 {
ns.currentIndex = rand.Int31n(int32(severLen))
} else {
ns.initRefreshSrvIfNeed(ctx)
}
ns.securityLogin.Login()
ns.securityLogin.AutoRefresh(ctx)
return &ns, nil
}
func (server *NacosServer) callConfigServer(api string, params map[string]string, newHeaders map[string]string, method string, curServer string, contextPath string) (result string, err error) {
func (server *NacosServer) callConfigServer(api string, params map[string]string, newHeaders map[string]string,
method string, curServer string, contextPath string, timeoutMS uint64) (result string, err error) {
start := time.Now()
if contextPath == "" {
contextPath = constant.WEB_CONTEXT
}
signHeaders := getSignHeaders(params, newHeaders)
url := curServer + contextPath + api
url := "http://" + curServer + contextPath + api
headers := map[string][]string{}
for k, v := range newHeaders {
if k != "accessKey" && k != "secretKey" {
headers[k] = []string{v}
}
}
headers["Client-Version"] = []string{constant.CLIENT_VERSION}
headers["User-Agent"] = []string{constant.CLIENT_VERSION}
//headers["Accept-Encoding"] = []string{"gzip,deflate,sdch"}
headers["Connection"] = []string{"Keep-Alive"}
headers["exConfigInfo"] = []string{"true"}
headers["RequestId"] = []string{uuid.NewV4().String()}
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=GBK"}
headers["Spas-AccessKey"] = []string{newHeaders["accessKey"]}
headers["Timestamp"] = []string{signHeaders["timeStamp"]}
headers["Spas-Signature"] = []string{signHeaders["Spas-Signature"]}
uid, err := uuid.NewV4()
if err != nil {
return
}
headers["RequestId"] = []string{uid.String()}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, server.timeoutMs, params)
response, err = server.httpAgent.Request(method, url, headers, timeoutMS, params)
monitor.GetConfigRequestMonitor(method, url, util.GetStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
if err != nil {
return
}
var bytes []byte
bytes, err = ioutil.ReadAll(response.Body)
bytes, err = io.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
return
}
result = string(bytes)
if response.StatusCode == 200 {
if response.StatusCode == constant.RESPONSE_CODE_SUCCESS {
return
} else {
err = nacos_error.NewNacosError(strconv.Itoa(response.StatusCode), string(bytes), nil)
@ -89,19 +145,25 @@ func (server *NacosServer) callConfigServer(api string, params map[string]string
}
func (server *NacosServer) callServer(api string, params map[string]string, method string, curServer string, contextPath string) (result string, err error) {
start := time.Now()
if contextPath == "" {
contextPath = constant.WEB_CONTEXT
}
url := "http://" + curServer + contextPath + api
url := curServer + contextPath + api
headers := map[string][]string{}
headers["Client-Version"] = []string{constant.CLIENT_VERSION}
headers["User-Agent"] = []string{constant.CLIENT_VERSION}
headers["Accept-Encoding"] = []string{"gzip,deflate,sdch"}
//headers["Accept-Encoding"] = []string{"gzip,deflate,sdch"}
headers["Connection"] = []string{"Keep-Alive"}
headers["RequestId"] = []string{uuid.NewV4().String()}
uid, err := uuid.NewV4()
if err != nil {
return
}
headers["RequestId"] = []string{uid.String()}
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=GBK"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, server.timeoutMs, params)
@ -109,106 +171,137 @@ func (server *NacosServer) callServer(api string, params map[string]string, meth
return
}
var bytes []byte
bytes, err = ioutil.ReadAll(response.Body)
bytes, err = io.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
return
}
result = string(bytes)
if response.StatusCode == 200 {
monitor.GetNamingRequestMonitor(method, api, util.GetStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
if response.StatusCode == constant.RESPONSE_CODE_SUCCESS {
return
} else {
err = errors.New(fmt.Sprintf("request return error code %d", response.StatusCode))
err = errors.Errorf("request return error code %d", response.StatusCode)
return
}
}
func (server *NacosServer) ReqConfigApi(api string, params map[string]string, headers map[string]string, method string) (string, error) {
func (server *NacosServer) ReqConfigApi(api string, params map[string]string, headers map[string]string, method string, timeoutMS uint64) (string, error) {
srvs := server.serverList
if srvs == nil || len(srvs) == 0 {
return "", errors.New("server list is empty")
}
server.InjectSecurityInfo(params, security.BuildConfigResource(params["tenant"], params["group"], params["dataId"]))
//only one server,retry request when error
var err error
var result string
if len(srvs) == 1 {
for i := 0; i < constant.REQUEST_DOMAIN_RETRY_TIME; i++ {
result, err = server.callConfigServer(api, params, headers, method, getAddress(srvs[0]), srvs[0].ContextPath)
result, err = server.callConfigServer(api, params, headers, method, getAddress(srvs[0]), srvs[0].ContextPath, timeoutMS)
if err == nil {
return result, nil
}
log.Printf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%s> , result:<%s> \n", api, method, utils.ToJsonString(params), err.Error(), result)
logger.Errorf("api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s>", api, method, util.ToJsonString(params), err, result)
}
return "", err
} else {
index := rand.Intn(len(srvs))
for i := 1; i <= len(srvs); i++ {
curServer := srvs[index]
result, err = server.callConfigServer(api, params, headers, method, getAddress(curServer), curServer.ContextPath)
result, err = server.callConfigServer(api, params, headers, method, getAddress(curServer), curServer.ContextPath, timeoutMS)
if err == nil {
return result, nil
}
log.Printf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%s> , result:<%s> \n", api, method, utils.ToJsonString(params), err.Error(), result)
logger.Errorf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s> \n", api, method, util.ToJsonString(params), err, result)
index = (index + i) % len(srvs)
}
return "", err
}
return "", errors.Wrapf(err, "retry %d times request failed!", constant.REQUEST_DOMAIN_RETRY_TIME)
}
func (server *NacosServer) ReqApi(api string, params map[string]string, method string) (string, error) {
func (server *NacosServer) ReqApi(api string, params map[string]string, method string, config constant.ClientConfig) (string, error) {
srvs := server.serverList
if srvs == nil || len(srvs) == 0 {
return "", errors.New("server list is empty")
}
server.InjectSecurityInfo(params, security.BuildNamingResource(params["namespaceId"], params["serviceName"], params["groupName"]))
//only one server,retry request when error
var err error
var result string
if len(srvs) == 1 {
for i := 0; i < constant.REQUEST_DOMAIN_RETRY_TIME; i++ {
result, err := server.callServer(api, params, method, getAddress(srvs[0]), srvs[0].ContextPath)
result, err = server.callServer(api, params, method, getAddress(srvs[0]), srvs[0].ContextPath)
if err == nil {
return result, nil
}
log.Printf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%s> , result:<%s> \n", api, method, utils.ToJsonString(params), err.Error(), result)
logger.Errorf("api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s>", api, method, util.ToJsonString(params), err, result)
}
return "", errors.New("retry " + strconv.Itoa(constant.REQUEST_DOMAIN_RETRY_TIME) + " times request failed!")
} else {
index := rand.Intn(len(srvs))
for i := 1; i <= len(srvs); i++ {
curServer := srvs[index]
result, err := server.callServer(api, params, method, getAddress(curServer), curServer.ContextPath)
result, err = server.callServer(api, params, method, getAddress(curServer), curServer.ContextPath)
if err == nil {
return result, nil
}
log.Printf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%s> , result:<%s> \n", api, method, utils.ToJsonString(params), err.Error(), result)
logger.Errorf("api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s>", api, method, util.ToJsonString(params), err, result)
index = (index + i) % len(srvs)
}
return "", errors.New("retry " + strconv.Itoa(constant.REQUEST_DOMAIN_RETRY_TIME) + " times request failed!")
}
return "", errors.Wrapf(err, "retry %d times request failed!", constant.REQUEST_DOMAIN_RETRY_TIME)
}
func (server *NacosServer) initRefreshSrvIfNeed() {
func (server *NacosServer) initRefreshSrvIfNeed(ctx context.Context) {
if server.endpoint == "" {
return
}
server.refreshServerSrvIfNeed()
if len(strings.TrimSpace(server.endpointContextPath)) == 0 {
server.endpointContextPath = "nacos"
}
if len(strings.TrimSpace(server.clusterName)) == 0 {
server.clusterName = "serverlist"
}
urlString := "http://" + server.endpoint + "/" + strings.TrimSpace(server.endpointContextPath) + "/" + strings.TrimSpace(server.clusterName)
if len(strings.TrimSpace(server.endpointQueryParams)) != 0 {
urlString += "?" + server.endpointQueryParams
}
logger.Infof("nacos address server url: <%s>", urlString)
server.refreshServerSrvIfNeed(urlString, server.endpointQueryHeader)
go func() {
time.Sleep(time.Duration(1) * time.Second)
server.refreshServerSrvIfNeed()
for {
select {
case <-ctx.Done():
return
default:
time.Sleep(time.Duration(10) * time.Second)
server.refreshServerSrvIfNeed(urlString, server.endpointQueryHeader)
}
}
}()
}
func (server *NacosServer) refreshServerSrvIfNeed() {
if utils.CurrentMillis()-server.lastSrvRefTime < server.vipSrvRefInterMills && len(server.serverList) > 0 {
func (server *NacosServer) refreshServerSrvIfNeed(urlString string, header map[string][]string) {
if util.CurrentMillis()-server.lastSrvRefTime < server.vipSrvRefInterMills && len(server.serverList) > 0 {
return
}
var list []string
urlString := "http://" + server.endpoint + "/nacos/serverlist"
result := server.httpAgent.RequestOnlyResult(http.MethodGet, urlString, nil, server.timeoutMs, nil)
result := server.httpAgent.RequestOnlyResult(http.MethodGet, urlString, header, server.timeoutMs, nil)
list = strings.Split(result, "\n")
log.Printf("[info] http nacos server list: <%s> \n", result)
var servers []constant.ServerConfig
contextPath := server.contextPath
if len(contextPath) == 0 {
contextPath = constant.WEB_CONTEXT
}
for _, line := range list {
if line != "" {
splitLine := strings.Split(strings.TrimSpace(line), ":")
@ -217,24 +310,30 @@ func (server *NacosServer) refreshServerSrvIfNeed() {
if len(splitLine) == 2 {
port, err = strconv.Atoi(splitLine[1])
if err != nil {
log.Printf("[ERROR] get port from server:<%s> error: <%s> \n", line, err.Error())
logger.Errorf("get port from server:<%s> error: <%+v>", line, err)
continue
}
}
servers = append(servers, constant.ServerConfig{IpAddr: splitLine[0], Port: uint64(port), ContextPath: constant.WEB_CONTEXT})
servers = append(servers, constant.ServerConfig{Scheme: constant.DEFAULT_SERVER_SCHEME, IpAddr: splitLine[0], Port: uint64(port), ContextPath: contextPath})
}
}
if len(servers) > 0 {
if !reflect.DeepEqual(server.serverList, servers) {
server.Lock()
log.Printf("[info] server list is updated, old: <%v>,new:<%v> \n", server.serverList, servers)
var serverPrev = server.serverList
logger.Infof("server list is updated, old: <%v>,new:<%v>", serverPrev, servers)
server.serverList = servers
server.lastSrvRefTime = utils.CurrentMillis()
if serverPrev != nil {
server.ServerSrcChangeSignal <- struct{}{}
}
server.lastSrvRefTime = util.CurrentMillis()
server.securityLogin.UpdateServerList(servers)
server.Unlock()
}
}
return
}
@ -242,42 +341,25 @@ func (server *NacosServer) GetServerList() []constant.ServerConfig {
return server.serverList
}
func (server *NacosServer) InjectSecurityInfo(param map[string]string, resource security.RequestResource) {
securityInfo := server.securityLogin.GetSecurityInfo(resource)
for k, v := range securityInfo {
param[k] = v
}
}
func getAddress(cfg constant.ServerConfig) string {
return cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
}
func getSignHeaders(params map[string]string, newHeaders map[string]string) map[string]string {
resource := ""
if len(params["tenant"]) != 0 {
resource = params["tenant"] + "+" + params["group"]
} else {
resource = params["group"]
if strings.Index(cfg.IpAddr, "http://") >= 0 || strings.Index(cfg.IpAddr, "https://") >= 0 {
return cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
}
return cfg.Scheme + "://" + cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
}
headers := map[string]string{}
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
headers["timeStamp"] = timeStamp
signature := ""
if resource == "" {
signature = signWithhmacSHA1Encrypt(timeStamp, newHeaders["secretKey"])
} else {
signature = signWithhmacSHA1Encrypt(resource+"+"+timeStamp, newHeaders["secretKey"])
func (server *NacosServer) GetNextServer() (constant.ServerConfig, error) {
serverLen := len(server.GetServerList())
if serverLen == 0 {
return constant.ServerConfig{}, errors.New("server is empty")
}
headers["Spas-Signature"] = signature
return headers
}
func signWithhmacSHA1Encrypt(encryptText, encryptKey string) string {
//hmac ,use sha1
key := []byte(encryptKey)
mac := hmac.New(sha1.New, key)
mac.Write([]byte(encryptText))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
index := atomic.AddInt32(&server.currentIndex, 1) % int32(serverLen)
return server.GetServerList()[index], nil
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 1999-2020 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.
*/
package nacos_server
import (
"context"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/stretchr/testify/assert"
)
func Test_getAddressWithScheme(t *testing.T) {
var serverConfigTest = constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "console.nacos.io",
Scheme: "https",
}
address := getAddress(serverConfigTest)
assert.Equal(t, "https://console.nacos.io:80", address)
}
func Test_getAddressWithoutScheme(t *testing.T) {
serverConfigTest := constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "http://console.nacos.io",
}
assert.Equal(t, "http://console.nacos.io:80", getAddress(serverConfigTest))
serverConfigTest.IpAddr = "https://console.nacos.io"
assert.Equal(t, "https://console.nacos.io:80", getAddress(serverConfigTest))
}
func buildNacosServer(clientConfig constant.ClientConfig) (*NacosServer, error) {
return NewNacosServer(context.Background(),
[]constant.ServerConfig{*constant.NewServerConfig("http://console.nacos.io", 80)},
clientConfig,
&http_agent.HttpAgent{},
1000,
"",
nil)
}
func TestNacosServer_InjectSignForNamingHttp_NoAk(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "",
SecretKey: "",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
param["groupName"] = "g-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Empty(t, param["ak"])
assert.Empty(t, param["data"])
assert.Empty(t, param["signature"])
}
func TestNacosServer_InjectSignForNamingHttp_WithGroup(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
param["groupName"] = "g-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.Contains(t, param["data"], "@@g-0@@s-0")
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_InjectSignForNamingHttp_WithoutGroup(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.NotContains(t, param["data"], "@@g-0@@s-0")
assert.Contains(t, param["data"], "@@s-0")
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_InjectSignForNamingHttp_WithoutServiceName(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["groupName"] = "g-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.Contains(t, param["data"], "@@")
assert.Regexp(t, "\\d+", param["data"])
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_InjectSignForNamingHttp_WithoutServiceNameAndGroup(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["serviceName"], param["groupName"]))
assert.Equal(t, "123", param["ak"])
assert.NotContains(t, param["data"], "@@")
assert.Regexp(t, "\\d+", param["data"])
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_UpdateServerListForSecurityLogin(t *testing.T) {
endpoint := "console.nacos.io:80"
clientConfig := constant.ClientConfig{
Username: "nacos",
Password: "nacos",
NamespaceId: "namespace_1",
Endpoint: endpoint,
EndpointContextPath: "nacos",
ClusterName: "serverlist",
AppendToStdout: true,
}
server, err := NewNacosServer(context.Background(),
nil,
clientConfig,
&http_agent.HttpAgent{},
1000,
endpoint,
nil)
if err != nil {
t.FailNow()
}
nacosAuthClient := server.securityLogin.Clients[0]
client, ok := nacosAuthClient.(*security.NacosAuthClient)
assert.True(t, ok)
assert.Equal(t, server.GetServerList(), client.GetServerList())
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 1999-2020 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.
*/
package rpc
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"google.golang.org/grpc"
)
type IConnection interface {
request(request rpc_request.IRequest, timeoutMills int64, client *RpcClient) (rpc_response.IResponse, error)
close()
getConnectionId() string
getServerInfo() ServerInfo
setAbandon(flag bool)
getAbandon() bool
}
type Connection struct {
conn *grpc.ClientConn
connectionId string
abandon bool
serverInfo ServerInfo
}
func (c *Connection) getConnectionId() string {
return c.connectionId
}
func (c *Connection) getServerInfo() ServerInfo {
return c.serverInfo
}
func (c *Connection) setAbandon(flag bool) {
c.abandon = flag
}
func (c *Connection) getAbandon() bool {
return c.abandon
}
func (c *Connection) close() {
_ = c.conn.Close()
}

View File

@ -1,9 +1,11 @@
/*
* Copyright 1999-2020 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
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -12,7 +14,13 @@
* limitations under the License.
*/
package auth
package rpc
type Credential interface {
type IConnectionEventListener interface {
//notify when connected to server.
OnConnected()
//notify when disconnected to server.
OnDisConnect()
}

View File

@ -0,0 +1,25 @@
package rpc
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
)
type MockConnection struct {
}
func (m *MockConnection) request(request rpc_request.IRequest, timeoutMills int64, client *RpcClient) (rpc_response.IResponse, error) {
return nil, nil
}
func (m *MockConnection) close() {
}
func (m *MockConnection) getConnectionId() string {
return ""
}
func (m *MockConnection) getServerInfo() ServerInfo {
return ServerInfo{}
}
func (m *MockConnection) setAbandon(flag bool) {
}

View File

@ -0,0 +1,352 @@
/*
* Copyright 1999-2020 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.
*/
package rpc
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"google.golang.org/grpc/credentials"
"io"
"log"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
nacos_grpc_service "github.com/nacos-group/nacos-sdk-go/v2/api/grpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
)
type GrpcClient struct {
*RpcClient
*constant.TLSConfig
}
func NewGrpcClient(ctx context.Context, clientName string, nacosServer *nacos_server.NacosServer, tlsConfig *constant.TLSConfig) *GrpcClient {
rpcClient := &GrpcClient{
&RpcClient{
ctx: ctx,
name: clientName,
labels: make(map[string]string, 8),
rpcClientStatus: INITIALIZED,
eventChan: make(chan ConnectionEvent, 1),
reconnectionChan: make(chan ReconnectContext, 1),
nacosServer: nacosServer,
mux: new(sync.Mutex),
}, tlsConfig,
}
rpcClient.RpcClient.lastActiveTimestamp.Store(time.Now())
rpcClient.executeClient = rpcClient
listeners := make([]IConnectionEventListener, 0, 8)
rpcClient.connectionEventListeners.Store(listeners)
return rpcClient
}
func getMaxCallRecvMsgSize() int {
maxCallRecvMsgSizeInt, err := strconv.Atoi(os.Getenv("nacos.remote.client.grpc.maxinbound.message.size"))
if err != nil {
return 10 * 1024 * 1024
}
return maxCallRecvMsgSizeInt
}
func getInitialWindowSize() int32 {
initialWindowSize, err := strconv.Atoi(os.Getenv("nacos.remote.client.grpc.initial.window.size"))
if err != nil {
return 10 * 1024 * 1024
}
return int32(initialWindowSize)
}
func getInitialConnWindowSize() int32 {
initialConnWindowSize, err := strconv.Atoi(os.Getenv("nacos.remote.client.grpc.initial.conn.window.size"))
if err != nil {
return 10 * 1024 * 1024
}
return int32(initialConnWindowSize)
}
func getTLSCredentials(tlsConfig *constant.TLSConfig, serverInfo ServerInfo) credentials.TransportCredentials {
logger.Infof("build tls config for connecting to server %s,tlsConfig = %s", serverInfo.serverIp, tlsConfig)
certPool, err := x509.SystemCertPool()
if err != nil {
log.Fatalf("load root cert pool fail : %v", err)
}
if len(tlsConfig.CaFile) != 0 {
cert, err := os.ReadFile(tlsConfig.CaFile)
if err != nil {
fmt.Errorf("err, %v", err)
}
if ok := certPool.AppendCertsFromPEM(cert); !ok {
fmt.Errorf("failed to append ca certs")
}
}
config := tls.Config{
InsecureSkipVerify: tlsConfig.TrustAll,
RootCAs: certPool,
Certificates: []tls.Certificate{},
}
if len(tlsConfig.CertFile) != 0 && len(tlsConfig.KeyFile) != 0 {
cert, err := tls.LoadX509KeyPair(tlsConfig.CertFile, tlsConfig.KeyFile)
if err != nil {
log.Fatalf("load cert fail : %v", err)
}
config.Certificates = append(config.Certificates, cert)
}
credentials := credentials.NewTLS(&config)
return credentials
}
func getInitialGrpcTimeout() int32 {
initialGrpcTimeout, err := strconv.Atoi(os.Getenv("nacos.remote.client.grpc.timeout"))
if err != nil {
return constant.DEFAULT_TIMEOUT_MILLS
}
return int32(initialGrpcTimeout)
}
func getKeepAliveTimeMillis() keepalive.ClientParameters {
keepAliveTimeMillisInt, err := strconv.Atoi(os.Getenv("nacos.remote.grpc.keep.alive.millis"))
var keepAliveTime time.Duration
if err != nil {
keepAliveTime = 60 * 1000 * time.Millisecond
} else {
keepAliveTime = time.Duration(keepAliveTimeMillisInt) * time.Millisecond
}
return keepalive.ClientParameters{
Time: keepAliveTime, // send pings every 60 seconds if there is no activity
Timeout: 20 * time.Second, // wait 20 second for ping ack before considering the connection dead
PermitWithoutStream: true, // send pings even without active streams
}
}
func (c *GrpcClient) createNewConnection(serverInfo ServerInfo) (*grpc.ClientConn, error) {
var opts []grpc.DialOption
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(getMaxCallRecvMsgSize())))
opts = append(opts, grpc.WithKeepaliveParams(getKeepAliveTimeMillis()))
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithInitialWindowSize(getInitialWindowSize()))
opts = append(opts, grpc.WithInitialConnWindowSize(getInitialConnWindowSize()))
c.getEnvTLSConfig(c.TLSConfig)
if c.TLSConfig.Enable {
logger.Infof(" tls enable ,trying to connection to server %s with tls config %s", serverInfo.serverIp, c.TLSConfig)
opts = append(opts, grpc.WithTransportCredentials(getTLSCredentials(c.TLSConfig, serverInfo)))
}
rpcPort := serverInfo.serverGrpcPort
if rpcPort == 0 {
rpcPort = serverInfo.serverPort + c.rpcPortOffset()
}
return grpc.Dial(serverInfo.serverIp+":"+strconv.FormatUint(rpcPort, 10), opts...)
}
func (c *GrpcClient) getEnvTLSConfig(config *constant.TLSConfig) {
logger.Infof("check tls config ", config)
if config.Appointed == true {
return
}
logger.Infof("try to get tls config from env")
enableTls, err := strconv.ParseBool(os.Getenv("nacos_remote_client_rpc_tls_enable"))
if err == nil {
config.Enable = enableTls
logger.Infof("get tls config from env key = enableTls value = %s", enableTls)
}
if enableTls != true {
logger.Infof(" tls config from env is not enable")
return
}
trustAll, err := strconv.ParseBool(os.Getenv("nacos_remote_client_rpc_tls_trustAll"))
if err == nil {
config.TrustAll = trustAll
logger.Infof("get tls config from env key = trustAll value = %s", trustAll)
}
config.CaFile = os.Getenv("nacos_remote_client_rpc_tls_trustCollectionChainPath")
logger.Infof("get tls config from env key = trustCollectionChainPath value = %s", config.CaFile)
config.CertFile = os.Getenv("nacos_remote_client_rpc_tls_certChainFile")
logger.Infof("get tls config from env key = certChainFile value = %s", config.CertFile)
config.KeyFile = os.Getenv("nacos_remote_client_rpc_tls_certPrivateKey")
logger.Infof("get tls config from env key = certPrivateKey value = %s", config.KeyFile)
}
func (c *GrpcClient) connectToServer(serverInfo ServerInfo) (IConnection, error) {
var client nacos_grpc_service.RequestClient
var biStreamClient nacos_grpc_service.BiRequestStreamClient
conn, err := c.createNewConnection(serverInfo)
if err != nil {
return nil, errors.Errorf("grpc create new connection failed , err:%v", err)
}
client = nacos_grpc_service.NewRequestClient(conn)
response, err := serverCheck(client)
if err != nil {
_ = conn.Close()
return nil, errors.Errorf("server check request failed , err:%v", err)
}
serverCheckResponse := response.(*rpc_response.ServerCheckResponse)
biStreamClient = nacos_grpc_service.NewBiRequestStreamClient(conn)
biStreamRequestClient, err := biStreamClient.RequestBiStream(context.Background())
if err != nil {
return nil, errors.Errorf("create biStreamRequestClient failed , err:%v", err)
}
grpcConn := NewGrpcConnection(serverInfo, serverCheckResponse.ConnectionId, conn, client, biStreamRequestClient)
c.bindBiRequestStream(biStreamRequestClient, grpcConn)
err = c.sendConnectionSetupRequest(grpcConn)
return grpcConn, err
}
func (c *GrpcClient) sendConnectionSetupRequest(grpcConn *GrpcConnection) error {
csr := rpc_request.NewConnectionSetupRequest()
csr.ClientVersion = constant.CLIENT_VERSION
csr.Tenant = c.Tenant
csr.Labels = c.labels
csr.ClientAbilities = c.clientAbilities
err := grpcConn.biStreamSend(convertRequest(csr))
if err != nil {
logger.Warnf("send connectionSetupRequest error:%v", err)
}
time.Sleep(100 * time.Millisecond)
return err
}
func (c *GrpcClient) getConnectionType() ConnectionType {
return GRPC
}
func (c *GrpcClient) rpcPortOffset() uint64 {
return constant.RpcPortOffset
}
func (c *GrpcClient) bindBiRequestStream(streamClient nacos_grpc_service.BiRequestStream_RequestBiStreamClient, grpcConn *GrpcConnection) {
go func() {
for {
select {
case <-streamClient.Context().Done():
logger.Warnf("connectionId %s stream client close", grpcConn.getConnectionId())
return
default:
payload, err := streamClient.Recv()
if err != nil {
running := c.IsRunning()
abandon := grpcConn.getAbandon()
if c.IsRunning() && !abandon {
if err == io.EOF {
logger.Infof("connectionId %s request stream onCompleted, switch server", grpcConn.getConnectionId())
} else {
logger.Errorf("connectionId %s request stream error, switch server, error=%v", grpcConn.getConnectionId(), err)
}
if atomic.CompareAndSwapInt32((*int32)(&c.rpcClientStatus), int32(RUNNING), int32(UNHEALTHY)) {
c.switchServerAsync(ServerInfo{}, false)
return
}
} else {
logger.Errorf("connectionId %s received error event, isRunning:%v, isAbandon=%v, error=%v", grpcConn.getConnectionId(), running, abandon, err)
return
}
} else {
c.handleServerRequest(payload, grpcConn)
}
}
}
}()
}
func serverCheck(client nacos_grpc_service.RequestClient) (rpc_response.IResponse, error) {
var response rpc_response.ServerCheckResponse
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(getInitialGrpcTimeout())*time.Millisecond)
defer cancel()
for i := 0; i <= 30; i++ {
payload, err := client.Request(ctx, convertRequest(rpc_request.NewServerCheckRequest()))
if err != nil {
return nil, err
}
err = json.Unmarshal(payload.GetBody().Value, &response)
if err != nil {
return nil, err
}
// check if the server is ready, if not, wait 1 second and try again
if response.GetErrorCode() >= 300 && response.GetErrorCode() < 400 {
// if we wait 30 second, but the server is not ready,then throw this error
if i == 30 {
return nil, errors.New("the nacos server is not ready to work in 30 seconds, connect to server failed")
}
time.Sleep(1 * time.Second)
continue
}
break
}
return &response, nil
}
func (c *GrpcClient) handleServerRequest(p *nacos_grpc_service.Payload, grpcConn *GrpcConnection) {
client := c.GetRpcClient()
payLoadType := p.GetMetadata().GetType()
handlerMapping, ok := client.serverRequestHandlerMapping.Load(payLoadType)
if !ok {
logger.Errorf("%s Unsupported payload type", grpcConn.getConnectionId())
return
}
mapping := handlerMapping.(ServerRequestHandlerMapping)
serverRequest := mapping.serverRequest()
err := json.Unmarshal(p.GetBody().Value, serverRequest)
if err != nil {
logger.Errorf("%s Fail to json Unmarshal for request:%s, ackId->%s", grpcConn.getConnectionId(),
serverRequest.GetRequestType(), serverRequest.GetRequestId())
return
}
serverRequest.PutAllHeaders(p.GetMetadata().Headers)
response := mapping.handler.RequestReply(serverRequest, client)
if response == nil {
logger.Warnf("%s Fail to process server request, ackId->%s", grpcConn.getConnectionId(),
serverRequest.GetRequestId())
return
}
response.SetRequestId(serverRequest.GetRequestId())
err = grpcConn.biStreamSend(convertResponse(response))
if err != nil && err != io.EOF {
logger.Warnf("%s Fail to send response:%s,ackId->%s", grpcConn.getConnectionId(),
response.GetResponseType(), serverRequest.GetRequestId())
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 1999-2020 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.
*/
package rpc
import (
"context"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
nacos_grpc_service "github.com/nacos-group/nacos-sdk-go/v2/api/grpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/grpc"
)
type GrpcConnection struct {
*Connection
client nacos_grpc_service.RequestClient
biStreamClient nacos_grpc_service.BiRequestStream_RequestBiStreamClient
}
func NewGrpcConnection(serverInfo ServerInfo, connectionId string, conn *grpc.ClientConn,
client nacos_grpc_service.RequestClient, biStreamClient nacos_grpc_service.BiRequestStream_RequestBiStreamClient) *GrpcConnection {
return &GrpcConnection{
Connection: &Connection{
serverInfo: serverInfo,
connectionId: connectionId,
abandon: false,
conn: conn,
},
client: client,
biStreamClient: biStreamClient,
}
}
func (g *GrpcConnection) request(request rpc_request.IRequest, timeoutMills int64, client *RpcClient) (rpc_response.IResponse, error) {
p := convertRequest(request)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMills)*time.Millisecond)
defer cancel()
responsePayload, err := g.client.Request(ctx, p)
if err != nil {
logger.Debugf("%s grpc request nacos server failed, request=%+v, err=%v ", g.getConnectionId(), p, err)
return nil, err
}
responseFunc, ok := rpc_response.ClientResponseMapping[responsePayload.Metadata.GetType()]
if !ok {
return nil, errors.Errorf("request:%s,unsupported response type:%s", request.GetRequestType(),
responsePayload.Metadata.GetType())
}
logger.Debugf("%s grpc request nacos server success, request=%+v, response=%s", g.getConnectionId(), p, string(responsePayload.GetBody().Value))
return rpc_response.InnerResponseJsonUnmarshal(responsePayload.GetBody().Value, responseFunc)
}
func (g *GrpcConnection) close() {
g.Connection.close()
}
func (g *GrpcConnection) biStreamSend(payload *nacos_grpc_service.Payload) error {
return g.biStreamClient.Send(payload)
}
func convertRequest(r rpc_request.IRequest) *nacos_grpc_service.Payload {
Metadata := nacos_grpc_service.Metadata{
Type: r.GetRequestType(),
Headers: r.GetHeaders(),
ClientIp: util.LocalIP(),
}
return &nacos_grpc_service.Payload{
Metadata: &Metadata,
Body: &anypb.Any{Value: []byte(r.GetBody(r))},
}
}
func convertResponse(r rpc_response.IResponse) *nacos_grpc_service.Payload {
Metadata := nacos_grpc_service.Metadata{
Type: r.GetResponseType(),
ClientIp: util.LocalIP(),
}
return &nacos_grpc_service.Payload{
Metadata: &Metadata,
Body: &anypb.Any{Value: []byte(r.GetBody())},
}
}

Some files were not shown because too many files have changed in this diff Show More