Compare commits

...

101 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
136 changed files with 12213 additions and 3619 deletions

View File

@ -14,8 +14,7 @@ jobs:
strategy:
matrix:
config:
- go_version: 1.13
- go_version: 1.14
- go_version: 1.21
steps:
- name: Set up Go 1.x
@ -27,9 +26,6 @@ jobs:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Install goimports
run: go get golang.org/x/tools/cmd/goimports
- name: Test
run: |
diff -u <(echo -n) <(gofmt -d -s .)

431
README.md
View File

@ -9,52 +9,60 @@
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.12
Supported Nacos version over 1.x
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
$ go get -u github.com/nacos-group/nacos-sdk-go/v2
```
## Quick Examples
* ClientConfig
```go
constant.ClientConfig{
TimeoutMs uint64 // timeout for requesting Nacos server, default value is 10000ms
NamespaceId string // the namespaceId of Nacos.When namespace is public, fill in the blank string here.
AppName string // the appName
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
OpenKMS bool // it's to open kms,default is false. https://help.aliyun.com/product/28933.html
CacheDir string // the directory for persist nacos service info,default value is current path
UpdateThreadNum int // the number of gorutine 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
LogSampling *ClientLogSamplingConfig // the sampling config of log
ContextPath string // the nacos server contextpath
LogRollingConfig *ClientLogRollingConfig // the log rolling config
}
```
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{
ContextPath string // the nacos server context path
IpAddr string // the nacos server address
Port uint64 // the nacos server port
Scheme string // the nacos server scheme
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
}
```
<b>NoteWe can config multiple ServerConfig,the client will rotate request the servers</b>
@ -62,110 +70,112 @@ constant.ServerConfig{
### Create client
```go
//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 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 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,
})
// 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 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,
},
)
// Another way of create config client for dynamic configuration (recommend)
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",
}
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,
},
)
// a more graceful way to create config client
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
},
)
```
### Service Discovery
* Register instanceRegisterInstance
@ -173,18 +183,18 @@ client, err := clients.NewConfigClient(
```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 value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
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
})
```
* Deregister instanceDeregisterInstance
@ -192,13 +202,13 @@ success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{
```go
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
})
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
})
```
@ -207,35 +217,37 @@ success, err := namingClient.DeregisterInstance(vo.DeregisterInstanceParam{
```go
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
})
ServiceName: "demo.go",
Clusters: []string{"cluster-a"}, // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
```
* Get all instancesSelectAllInstances
```go
// 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
})
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
})
```
* Get instances SelectInstances
```go
// 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,
})
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,
})
```
@ -243,12 +255,12 @@ instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
```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
})
// 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
})
```
@ -257,15 +269,15 @@ instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanc
```go
// 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.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
// 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))
},
})
```
@ -274,24 +286,25 @@ err := namingClient.Subscribe(vo.SubscribeParam{
```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.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
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 := client.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,
}),
serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,
}),
```
@ -302,9 +315,9 @@ serviceInfos, err := client.GetAllServicesInfo(vo.GetAllServiceInfoParam{
```go
success, err := configClient.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
```
@ -313,8 +326,8 @@ success, err := configClient.PublishConfig(vo.ConfigParam{
```go
success, err = configClient.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: "dataId",
Group: "group"})
```
@ -323,8 +336,9 @@ success, err = configClient.DeleteConfig(vo.ConfigParam{
```go
content, err := configClient.GetConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: "dataId",
Group: "group"})
```
@ -333,56 +347,65 @@ content, err := configClient.GetConfig(vo.ConfigParam{
```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)
},
})
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",
})
DataId: "dataId",
Group: "group",
})
```
* Search config: SearchConfig
```go
configPage, err := configClient.SearchConfig(vo.SearchConfigParam{
Search: "blur",
DataId: "",
Group: "",
PageNo: 1,
PageSize: 10,
})
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.
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.
* 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.

View File

@ -9,14 +9,14 @@
Nacos-sdk-go是Nacos的Go语言客户端它实现了服务发现和动态配置的功能
## 使用限制
支持Go>v1.12版本
支持Go>=v1.15版本
支持Nacos>1.x版本
支持Nacos>2.x版本
## 安装
使用`go get`安装SDK
```sh
$ go get -u github.com/nacos-group/nacos-sdk-go
$ go get -u github.com/nacos-group/nacos-sdk-go/v2
```
## 快速使用
* ClientConfig
@ -24,8 +24,7 @@ $ go get -u github.com/nacos-group/nacos-sdk-go
```go
constant.ClientConfig{
TimeoutMs uint64 // 请求Nacos服务端的超时时间默认是10000ms
NamespaceId string // ACM的命名空间Id
AppName string // App名称
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用于配置中心的鉴权
@ -39,9 +38,9 @@ constant.ClientConfig{
Username string // Nacos服务端的API鉴权Username
Password string // Nacos服务端的API鉴权Password
LogDir string // 日志存储路径
LogLevel string // 日志默认级别值必须是debug,info,warn,error默认值是info
LogSampling *ClientLogSamplingConfig // 日志采样配置
LogRollingConfig *ClientLogRollingConfig // 日志归档配置
RotateTime string // 日志轮转周期比如30m, 1h, 24h, 默认是24h
MaxAge int64 // 日志最大文件数默认3
LogLevel string // 日志默认级别值必须是debug,info,warn,error默认值是info
}
```
@ -49,10 +48,11 @@ constant.ClientConfig{
```go
constant.ServerConfig{
ContextPath string // Nacos的ContextPath
ContextPath string // Nacos的ContextPath,默认/nacos在2.0中不需要设置
IpAddr string // Nacos的服务地址
Port uint64 // Nacos的服务端口
Scheme string // Nacos的服务地址前缀
Scheme string // Nacos的服务地址前缀默认http在2.0中不需要设置
GrpcPort uint64 // Nacos的 grpc 服务端口, 默认为 服务端口+1000, 不是必填
}
```
@ -63,7 +63,7 @@ constant.ServerConfig{
```go
// 创建clientConfig
clientConfig := constant.ClientConfig{
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", // 如果需要支持多namespace我们可以场景多个client,它们有不同的NamespaceId。当namespace是public时此处填空字符串。
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", // 如果需要支持多namespace我们可以创建多个client,它们有不同的NamespaceId。当namespace是public时此处填空字符串。
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
@ -103,24 +103,24 @@ serverConfigs := []constant.ServerConfig{
"console1.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos")
constant.WithContextPath("/nacos"),
),
*constant.NewServerConfig(
"console2.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos")
constant.WithContextPath("/nacos"),
),
}
// 创建服务发现客户端
_, _ = clients.CreateNamingClient(map[string]interface{}{
_, _ := clients.CreateNamingClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
// 创建动态配置客户端
_, _ = clients.CreateConfigClient(map[string]interface{}{
_, _ := clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
@ -262,7 +262,7 @@ err := namingClient.Subscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // 默认值DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
SubscribeCallback: func(services []model.SubscribeService, err error) {
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
@ -277,7 +277,7 @@ err := namingClient.Unsubscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // 默认值DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // 默认值DEFAULT
SubscribeCallback: func(services []model.SubscribeService, err error) {
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
@ -287,7 +287,7 @@ err := namingClient.Unsubscribe(vo.SubscribeParam{
* 获取服务名列表:GetAllServicesInfo
```go
serviceInfos, err := client.GetAllServicesInfo(vo.GetAllServiceInfoParam{
serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,

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

@ -296,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

@ -19,43 +19,58 @@ package cache
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/file"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"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 {
if runtime.GOOS == constant.OS_WINDOWS {
cacheKey = strings.ReplaceAll(cacheKey, ":", constant.WINDOWS_LEGAL_NAME_SPLITER)
}
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) {
file.MkdirIfNecessary(cacheDir)
sb, _ := json.Marshal(service)
domFileName := GetFileName(util.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 {
logger.Errorf("failed to write name cache:%s ,value:%s ,err:%+v", domFileName, string(sb), err)
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 {
logger.Errorf("read cacheDir:%s failed!err:%+v", cacheDir, err)
return nil
@ -63,9 +78,9 @@ func ReadServicesFromFile(cacheDir string) map[string]model.Service {
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 {
logger.Errorf("failed to read name cache file:%s,err:%+v ", fileName, err)
logger.Errorf("failed to read name cache file:%s,err:%v ", fileName, err)
continue
}
@ -75,28 +90,114 @@ func ReadServicesFromFile(cacheDir string) map[string]model.Service {
if service == nil {
continue
}
serviceMap[f.Name()] = *service
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
serviceMap[cacheKey] = *service
}
logger.Info("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) {
file.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 {
logger.Errorf("failed to write config cache:%s ,value:%s ,err:%+v", fileName, content, err)
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:%+v ", fileName, err))
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)
}

View File

@ -1,36 +1,127 @@
/*
* 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 (
"runtime"
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"math/rand"
"os"
"strconv"
"testing"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/stretchr/testify/assert"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
)
func TestGetFileName(t *testing.T) {
var (
dir = file.GetCurrentPath()
group = "FILE_GROUP"
ns = "chasu"
)
name := GetFileName("nacos@@providers:org.apache.dubbo.UserProvider:hangzhou", "tmp")
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"
if runtime.GOOS == constant.OS_WINDOWS {
assert.Equal(t, name, "tmp\\nacos@@providers&&org.apache.dubbo.UserProvider&&hangzhou")
} else {
assert.Equal(t, name, "tmp/nacos@@providers:org.apache.dubbo.UserProvider:hangzhou")
}
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

@ -17,14 +17,15 @@
package clients
import (
"errors"
"github.com/pkg/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/nacos-group/nacos-sdk-go/vo"
"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"
)
// CreateConfigClient use to create config client
@ -33,7 +34,7 @@ func CreateConfigClient(properties map[string]interface{}) (iClient config_clien
return NewConfigClient(param)
}
//CreateNamingClient use to create a nacos naming client
// CreateNamingClient use to create a nacos naming client
func CreateNamingClient(properties map[string]interface{}) (iClient naming_client.INamingClient, err error) {
param := getConfigParam(properties)
return NewNamingClient(param)
@ -44,7 +45,7 @@ func NewConfigClient(param vo.NacosClientParam) (iClient config_client.IConfigCl
if err != nil {
return
}
config, err := config_client.NewConfigClient(nacosClient)
config, err := config_client.NewConfigClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}
@ -57,11 +58,11 @@ func NewNamingClient(param vo.NacosClientParam) (iClient naming_client.INamingCl
if err != nil {
return
}
naming, err := naming_client.NewNamingClient(nacosClient)
naming, err := naming_client.NewNamingClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}
iClient = &naming
iClient = naming
return
}
@ -84,9 +85,15 @@ func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, er
client := &nacos_client.NacosClient{}
if param.ClientConfig == nil {
// default clientConfig
_ = client.SetClientConfig(constant.ClientConfig{})
_ = client.SetClientConfig(constant.ClientConfig{
TimeoutMs: 10 * 1000,
BeatInterval: 5 * 1000,
})
} else {
_ = client.SetClientConfig(*param.ClientConfig)
err = client.SetClientConfig(*param.ClientConfig)
if err != nil {
return nil, err
}
}
if len(param.ServerConfigs) == 0 {
@ -95,8 +102,16 @@ func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, er
err = errors.New("server configs not found in properties")
return nil, err
}
_ = client.SetServerConfig([]constant.ServerConfig{})
_ = 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
@ -104,7 +119,9 @@ func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, er
}
if _, _err := client.GetHttpAgent(); _err != nil {
_ = client.SetHttpAgent(&http_agent.HttpAgent{})
if clientCfg, err := client.GetClientConfig(); err == nil {
_ = client.SetHttpAgent(&http_agent.HttpAgent{TlsConfig: clientCfg.TLSCfg})
}
}
iClient = client
return

View File

@ -1,26 +1,42 @@
package clients
import (
"net"
"reflect"
"testing"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
"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 TestSetConfigClient(t *testing.T) {
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(
"console.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos")),
ip,
8848,
),
}
cc := *constant.NewClientConfig(
constant.WithNamespaceId("e525eafa-f7d7-4029-83d9-008937f9d468"),
constant.WithNamespaceId("public"),
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
@ -49,5 +65,4 @@ func TestSetConfigClient(t *testing.T) {
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(nacosClientFromMap, nacosClientFromStruct))
})
}

View File

@ -17,57 +17,66 @@
package config_client
import (
"errors"
"context"
"fmt"
"math"
"net/url"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
"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/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"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"
)
type ConfigClient struct {
nacos_client.INacosClient
kmsClient *kms.Client
localConfigs []vo.ConfigParam
mutex sync.Mutex
configProxy ConfigProxy
configCacheDir string
currentTaskCount int32
cacheMap cache.ConcurrentMap
schedulerMap cache.ConcurrentMap
}
const (
perTaskConfigSize = 3000
executorErrDelay = 5 * time.Second
)
type ConfigClient struct {
ctx context.Context
cancel context.CancelFunc
nacos_client.INacosClient
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
}
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 {
@ -75,172 +84,190 @@ type cacheDataListener struct {
lastMd5 string
}
func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
config := &ConfigClient{
cacheMap: cache.NewConcurrentMap(),
schedulerMap: cache.NewConcurrentMap(),
}
config.schedulerMap.Set("root", true)
go config.delayScheduler(time.NewTimer(1*time.Millisecond), 500*time.Millisecond, "root", config.listenConfigExecutor())
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
return nil, err
}
loggerConfig := logger.Config{
LogFileName: constant.LOG_FILE_NAME,
Level: clientConfig.LogLevel,
Sampling: clientConfig.LogSampling,
LogRollingConfig: clientConfig.LogRollingConfig,
LogDir: clientConfig.LogDir,
CustomLogger: clientConfig.CustomLogger,
LogStdout: clientConfig.LogStdout,
if err = initLogger(clientConfig); err != nil {
return nil, err
}
err = logger.InitLogger(loggerConfig)
if err != nil {
return config, 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
}
logger.GetLogger().Infof("logDir:<%s> cacheDir:<%s>", clientConfig.LogDir, clientConfig.CacheDir)
config.configCacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config"
config.configProxy, err = NewConfigProxy(serverConfig, clientConfig, httpAgent)
config.configFilterChainManager = filter.NewConfigFilterChainManager()
if clientConfig.OpenKMS {
kmsClient, err := kms.NewClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
kmsEncryptionHandler := nacos_inner_encryption.NewKmsHandler()
nacos_inner_encryption.RegisterConfigEncryptionKmsPlugins(kmsEncryptionHandler, clientConfig)
encryptionFilter := filter.NewDefaultConfigEncryptionFilter(kmsEncryptionHandler)
err := filter.RegisterConfigFilterToChain(config.configFilterChainManager, encryptionFilter)
if err != nil {
return config, err
logger.Error(err)
}
config.kmsClient = kmsClient
}
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 {
logger.Errorf("getClientConfig catch error:%+v", err)
return
}
serverConfigs, err = client.GetServerConfig()
if err != nil {
logger.Errorf("getServerConfig catch error:%+v", err)
return
}
func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
return NewConfigClientWithRamCredentialProvider(nc, nil)
}
agent, err = client.GetHttpAgent()
if err != nil {
logger.Errorf("getHttpAgent catch error:%+v", err)
}
return
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 client.kmsClient != nil && strings.HasPrefix(dataId, "cipher-") {
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 "", fmt.Errorf("kms decrypt failed: %v", err)
}
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) encrypt(dataId, content string) (string, error) {
if client.kmsClient != nil && strings.HasPrefix(dataId, "cipher-") {
request := kms.CreateEncryptRequest()
request.Method = "POST"
request.Scheme = "https"
request.AcceptFormat = "json"
request.KeyId = "alias/acs/acm" // use default key
request.Plaintext = content
response, err := client.kmsClient.Encrypt(request)
if err != nil {
return "", fmt.Errorf("kms encrypt failed: %v", err)
}
content = response.CiphertextBlob
}
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
return "", "", err
}
if len(param.Group) <= 0 {
err = errors.New("[client.GetConfig] param.group can not be empty")
return "", err
param.Group = constant.DEFAULT_GROUP
}
clientConfig, _ := client.GetClientConfig()
cacheKey := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
content, err = client.configProxy.GetConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err != nil {
logger.Errorf("get config from server error:%+v ", err)
if _, ok := err.(*nacos_error.NacosError); ok {
nacosErr := err.(*nacos_error.NacosError)
if nacosErr.ErrorCode() == "404" {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, "")
logger.Warnf("[client.GetConfig] config not found, dataId: %s, group: %s, namespaceId: %s.", param.DataId, param.Group, clientConfig.NamespaceId)
return "", nil
}
if nacosErr.ErrorCode() == "403" {
return "", errors.New("get config forbidden")
}
}
content, err = cache.ReadConfigFromFile(cacheKey, client.configCacheDir)
if err != nil {
logger.Errorf("get config from cache error:%+v ", err)
return "", errors.New("read config from both server and cache fail")
}
} else {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, content)
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
}
param.Content, err = client.encrypt(param.DataId, param.Content)
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()
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
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.PublishConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if response != nil {
return client.buildResponse(response)
}
return false, err
}
func (client *ConfigClient) DeleteConfig(param vo.ConfigParam) (deleted bool, err error) {
@ -248,11 +275,22 @@ func (client *ConfigClient) DeleteConfig(param vo.ConfigParam) (deleted bool, er
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
}
// Cancel Listen Config
@ -264,32 +302,9 @@ func (client *ConfigClient) CancelListenConfig(param vo.ConfigParam) (err error)
}
client.cacheMap.Remove(util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId))
logger.Infof("Cancel listen config DataId:%s Group:%s", param.DataId, param.Group)
remakeId := int(math.Ceil(float64(client.cacheMap.Count()) / float64(perTaskConfigSize)))
currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))
if remakeId < currentTaskCount {
client.remakeCacheDataTaskId(remakeId)
}
return err
}
// Remake cache data taskId
func (client *ConfigClient) remakeCacheDataTaskId(remakeId int) {
for i := 0; i < remakeId; i++ {
count := 0
for _, key := range client.cacheMap.Keys() {
if count == perTaskConfigSize {
break
}
if value, ok := client.cacheMap.Get(key); ok {
cData := value.(cacheData)
cData.taskId = i
client.cacheMap.Set(key, cData)
}
count++
}
}
}
func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.ListenConfig] DataId can not be empty")
@ -312,10 +327,15 @@ func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
cData.isInitializing = true
} else {
var (
content string
md5Str string
content string
md5Str string
innerErr error
)
if content, _ = cache.ReadConfigFromFile(key, client.configCacheDir); len(content) > 0 {
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{
@ -331,189 +351,29 @@ func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
content: content,
md5: md5Str,
cacheDataListener: listener,
encryptedDataKey: encryptedDataKey,
taskId: client.cacheMap.Count() / perTaskConfigSize,
configClient: client,
}
}
client.cacheMap.Set(key, cData)
return
}
// Delay Scheduler
// initialDelay the time to delay first execution
// delay the delay between the termination of one execution and the commencement of the next
func (client *ConfigClient) delayScheduler(t *time.Timer, delay time.Duration, taskId string, execute func() error) {
for {
if v, ok := client.schedulerMap.Get(taskId); ok {
if !v.(bool) {
return
}
}
<-t.C
d := delay
if err := execute(); err != nil {
d = executorErrDelay
}
t.Reset(d)
}
}
// Listen for the configuration executor
func (client *ConfigClient) listenConfigExecutor() func() error {
return func() error {
listenerSize := client.cacheMap.Count()
taskCount := int(math.Ceil(float64(listenerSize) / float64(perTaskConfigSize)))
currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))
if taskCount > currentTaskCount {
for i := currentTaskCount; i < taskCount; i++ {
client.schedulerMap.Set(strconv.Itoa(i), true)
go client.delayScheduler(time.NewTimer(1*time.Millisecond), 10*time.Millisecond, strconv.Itoa(i), client.longPulling(i))
}
atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
} else if taskCount < currentTaskCount {
for i := taskCount; i < currentTaskCount; i++ {
if _, ok := client.schedulerMap.Get(strconv.Itoa(i)); ok {
client.schedulerMap.Set(strconv.Itoa(i), false)
}
}
atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
}
return nil
}
}
// Long polling listening configuration
func (client *ConfigClient) longPulling(taskId int) func() error {
return func() error {
var listeningConfigs string
initializationList := make([]cacheData, 0)
for _, key := range client.cacheMap.Keys() {
if value, ok := client.cacheMap.Get(key); ok {
cData := value.(cacheData)
if cData.taskId == taskId {
if cData.isInitializing {
initializationList = append(initializationList, cData)
}
if len(cData.tenant) > 0 {
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
cData.md5 + constant.SPLIT_CONFIG_INNER + cData.tenant + constant.SPLIT_CONFIG
} else {
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
cData.md5 + constant.SPLIT_CONFIG
}
}
}
}
if len(listeningConfigs) > 0 {
clientConfig, err := client.GetClientConfig()
if err != nil {
logger.Errorf("[checkConfigInfo.GetClientConfig] err: %+v", err)
return err
}
// http get
params := make(map[string]string)
params[constant.KEY_LISTEN_CONFIGS] = listeningConfigs
var changed string
changedTmp, err := client.configProxy.ListenConfig(params, len(initializationList) > 0, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err == nil {
changed = changedTmp
} else {
if _, ok := err.(*nacos_error.NacosError); ok {
changed = changedTmp
} else {
logger.Errorf("[client.ListenConfig] listen config error: %+v", err)
}
return err
}
for _, v := range initializationList {
v.isInitializing = false
client.cacheMap.Set(util.GetConfigCacheKey(v.dataId, v.group, v.tenant), v)
}
if len(strings.ToLower(strings.Trim(changed, " "))) == 0 {
logger.Info("[client.ListenConfig] no change")
} else {
logger.Info("[client.ListenConfig] config changed:" + changed)
client.callListener(changed, clientConfig.NamespaceId)
}
}
return nil
}
}
// Execute the Listener callback func()
func (client *ConfigClient) callListener(changed, tenant string) {
changedDecoded, _ := url.QueryUnescape(changed)
changedConfigs := strings.Split(changedDecoded, "\u0001")
for _, config := range changedConfigs {
attrs := strings.Split(config, "\u0002")
if len(attrs) >= 2 {
if value, ok := client.cacheMap.Get(util.GetConfigCacheKey(attrs[0], attrs[1], tenant)); ok {
cData := value.(cacheData)
content, err := client.getConfigInner(vo.ConfigParam{
DataId: cData.dataId,
Group: cData.group,
})
if err != nil {
logger.Errorf("[client.getConfigInner] DataId:[%s] Group:[%s] Error:[%+v]", cData.dataId, cData.group, err)
continue
}
cData.content = content
cData.md5 = util.Md5(content)
if cData.md5 != cData.cacheDataListener.lastMd5 {
go cData.cacheDataListener.listener(tenant, attrs[1], attrs[0], cData.content)
cData.cacheDataListener.lastMd5 = cData.md5
client.cacheMap.Set(util.GetConfigCacheKey(cData.dataId, cData.group, tenant), cData)
}
}
}
}
}
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) SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error) {
return client.searchConfigInner(param)
}
func (client *ConfigClient) PublishAggr(param vo.ConfigParam) (published bool,
err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.PublishAggr] param.dataId can not be empty")
}
if len(param.Group) <= 0 {
err = errors.New("[client.PublishAggr] param.group can not be empty")
}
if len(param.Content) <= 0 {
err = errors.New("[client.PublishAggr] param.content can not be empty")
}
if len(param.DatumId) <= 0 {
err = errors.New("[client.PublishAggr] param.DatumId can not be empty")
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.PublishAggProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
}
func (client *ConfigClient) CloseClient() {
client.mutex.Lock()
defer client.mutex.Unlock()
func (client *ConfigClient) RemoveAggr(param vo.ConfigParam) (published bool,
err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.DeleteAggr] param.dataId can not be empty")
if client.isClosed {
return
}
if len(param.Group) <= 0 {
err = errors.New("[client.DeleteAggr] param.group can not be empty")
}
if len(param.Content) <= 0 {
err = errors.New("[client.DeleteAggr] param.content can not be empty")
}
if len(param.DatumId) <= 0 {
err = errors.New("[client.DeleteAggr] param.DatumId can not be empty")
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.DeleteAggProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
client.configProxy.getRpcClient(client).Shutdown()
client.cancel()
client.isClosed = true
}
func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*model.ConfigPage, error) {
@ -527,13 +387,13 @@ func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*mode
param.PageSize = 10
}
clientConfig, _ := client.GetClientConfig()
configItems, err := client.configProxy.SearchConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
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, nil
return nil, errors.New("config not found")
}
if nacosErr.ErrorCode() == "403" {
return nil, errors.New("get config forbidden")
@ -543,3 +403,161 @@ func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*mode
}
return configItems, nil
}
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

@ -17,8 +17,8 @@
package config_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"
)
//go:generate mockgen -destination ../../mock/mock_config_client_interface.go -package mock -source=./config_client_interface.go
@ -65,5 +65,6 @@ type IConfigClient interface {
// pageSize option,default is 10
SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error)
PublishAggr(param vo.ConfigParam) (published bool, err error)
// CloseClient Close the GRPC client
CloseClient()
}

View File

@ -17,133 +17,154 @@
package config_client
import (
"context"
"errors"
"net/http"
"runtime"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"testing"
"time"
"github.com/golang/mock/gomock"
"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/mock"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/sirupsen/logrus"
"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"
)
var goVersion = runtime.Version()
var clientConfigTest = constant.ClientConfig{
TimeoutMs: 10000,
ListenInterval: 20000,
BeatInterval: 10000,
}
var clientConfigTestWithTenant = constant.ClientConfig{
TimeoutMs: 10000,
ListenInterval: 20000,
BeatInterval: 10000,
NamespaceId: "tenant",
}
var serverConfigTest = constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "console.nacos.io",
}
var serverConfigWithOptions = constant.NewServerConfig("console.nacos.io", 80, constant.WithContextPath("/nacos"))
var serverConfigWithOptions = constant.NewServerConfig("127.0.0.1", 8848)
var clientConfigWithOptions = constant.NewClientConfig(
constant.WithCustomLogger(mockLogger{}),
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 (
dataIdKey = goVersion + "dataId"
groupKey = goVersion + "group:env"
configNoChangeKey = goVersion + "ConfigNoChange"
multipleClientsKey = goVersion + "MultipleClients"
multipleClientsMultipleConfigsKey = goVersion + "MultipleClientsMultipleConfig"
cancelOneKey = goVersion + "CancelOne"
cancelOne1Key = goVersion + "CancelOne1"
cancelListenConfigKey = goVersion + "cancel_listen_config"
specialSymbolKey = goVersion + "special_symbol"
var clientTLsConfigWithOptions = constant.NewClientConfig(
constant.WithTimeoutMs(10*1000),
constant.WithBeatInterval(2*1000),
constant.WithNotLoadCacheAtStart(true),
/*constant.WithTLS(constant.TLSConfig{
Enable: true,
TrustAll: false,
CaFile: "mse-nacos-ca.cer",
}),*/
)
var configParamMapTest = map[string]string{
"dataId": dataIdKey,
"group": groupKey,
}
var configParamTest = vo.ConfigParam{
DataId: dataIdKey,
Group: groupKey,
}
var localConfigTest = vo.ConfigParam{
DataId: dataIdKey,
Group: groupKey,
DataId: "dataId",
Group: "group",
Content: "content",
}
var localConfigMapTest = map[string]string{
"dataId": dataIdKey,
"group": groupKey,
"content": "content",
}
var client *ConfigClient
func TestMain(m *testing.M) {
func createConfigClientTest() *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
nc.SetClientConfig(*clientConfigWithOptions)
nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ = NewConfigClient(&nc)
m.Run()
}
func createConfigClientHttpTest(mockHttpAgent http_agent.IHttpAgent) *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockHttpAgent)
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
func createConfigClientTestTls() *ConfigClient {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientTLsConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
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 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
}
type MockConfigProxyForUsingLocalDiskCache struct {
MockConfigProxy
}
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 := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
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: dataIdKey,
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_SearchConfig(t *testing.T) {
client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "groDEFAULT_GROUPup",
Content: "hello world!222222"})
client := createConfigClientTest()
_, _ = client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
Content: "hello world"})
configPage, err := client.SearchConfig(vo.SearchConfigParam{
Search: "accurate",
DataId: dataIdKey,
Group: "groDEFAULT_GROUPup",
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 10,
})
@ -151,8 +172,77 @@ func Test_SearchConfig(t *testing.T) {
assert.NotEmpty(t, configPage)
}
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.NotEmpty(t, configPage)
}
// 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 := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -161,18 +251,10 @@ func Test_PublishConfigWithoutDataId(t *testing.T) {
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutGroup(t *testing.T) {
_, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "",
Content: "content",
})
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutContent(t *testing.T) {
client := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
DataId: localConfigTest.DataId,
Group: "group",
Content: "",
})
@ -180,66 +262,26 @@ func Test_PublishConfigWithoutContent(t *testing.T) {
}
func Test_PublishConfig(t *testing.T) {
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
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_PublishConfigWithType(t *testing.T) {
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "group",
Content: "foo",
Type: vo.YAML,
})
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)
clientHttp := createConfigClientHttpTest(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 security"), nil)
success, err := clientHttp.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)
clientHttp := createConfigClientHttpTest(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 := clientHttp.PublishConfig(localConfigTest)
assert.NotNil(t, err)
assert.True(t, !success)
}
// DeleteConfig
func Test_DeleteConfig(t *testing.T) {
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
DataId: localConfigTest.DataId,
Group: "group",
Content: "hello world!"})
@ -247,50 +289,15 @@ func Test_DeleteConfig(t *testing.T) {
assert.True(t, success)
success, err = client.DeleteConfig(vo.ConfigParam{
DataId: dataIdKey,
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)
clientHttp := createConfigClientHttpTest(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 := clientHttp.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)
clientHttp := createConfigClientHttpTest(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 security"), nil)
success, err := clientHttp.DeleteConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutDataId(t *testing.T) {
client := createConfigClientTest()
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -299,206 +306,106 @@ func Test_DeleteConfigWithoutDataId(t *testing.T) {
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutGroup(t *testing.T) {
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "",
})
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
// ListenConfig
func TestListen(t *testing.T) {
// ListenConfig
t.Run("TestListenConfig", func(t *testing.T) {
key := util.GetConfigCacheKey(localConfigTest.DataId, localConfigTest.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, "")
var err error
var success bool
ch := make(chan string)
err = client.ListenConfig(vo.ConfigParam{
client := createConfigClientTest()
err := client.ListenConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
ch <- data
},
})
assert.Nil(t, err)
success, err = client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
assert.Nil(t, err)
assert.Equal(t, true, success)
select {
case c := <-ch:
assert.Equal(t, c, localConfigTest.Content)
case <-time.After(10 * time.Second):
assert.Errorf(t, errors.New("timeout"), "timeout")
}
})
// ListenConfig no dataId
t.Run("TestListenConfigNoDataId", func(t *testing.T) {
listenConfigParam := vo.ConfigParam{
Group: "gateway",
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
client := createConfigClientTest()
err := client.ListenConfig(listenConfigParam)
assert.Error(t, err)
})
// ListenConfig no change
t.Run("TestListenConfigNoChange", func(t *testing.T) {
key := util.GetConfigCacheKey(configNoChangeKey, localConfigTest.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, localConfigTest.Content)
}
// 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
var success bool
var content string
err = client.ListenConfig(vo.ConfigParam{
DataId: configNoChangeKey,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
content = "data"
},
})
assert.Nil(t, err)
success, err = client.PublishConfig(vo.ConfigParam{
DataId: configNoChangeKey,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
assert.Nil(t, err)
assert.Equal(t, true, success)
assert.Equal(t, content, "")
})
// Multiple clients listen to the same configuration file
t.Run("TestListenConfigWithMultipleClients", func(t *testing.T) {
ch := make(chan string)
listenConfigParam := vo.ConfigParam{
DataId: multipleClientsKey,
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
ch <- data
},
}
key := util.GetConfigCacheKey(listenConfigParam.DataId, listenConfigParam.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, "")
client.ListenConfig(listenConfigParam)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
nc.SetClientConfig(*clientConfigWithOptions)
nc.SetHttpAgent(&http_agent.HttpAgent{})
client1, _ := NewConfigClient(&nc)
client1.ListenConfig(listenConfigParam)
success, err := client.PublishConfig(vo.ConfigParam{
DataId: multipleClientsKey,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
assert.Nil(t, err)
assert.Equal(t, true, success)
select {
case c := <-ch:
assert.Equal(t, localConfigTest.Content, c)
case <-time.After(10 * time.Second):
assert.Errorf(t, errors.New("timeout"), "timeout")
}
})
// Multiple clients listen to multiple configuration files
t.Run("TestListenConfigWithMultipleClientsMultipleConfig", func(t *testing.T) {
ch := make(chan string)
listenConfigParam := vo.ConfigParam{
DataId: multipleClientsMultipleConfigsKey,
listenConfigParam1 := vo.ConfigParam{
DataId: localConfigTest.DataId + "1",
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
ch <- data
},
}
key := util.GetConfigCacheKey(listenConfigParam.DataId, listenConfigParam.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, "")
client.ListenConfig(listenConfigParam)
_ = client.ListenConfig(listenConfigParam)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
nc.SetClientConfig(*clientConfigWithOptions)
nc.SetHttpAgent(&http_agent.HttpAgent{})
client1, _ := NewConfigClient(&nc)
client1.ListenConfig(listenConfigParam)
success, err := client.PublishConfig(vo.ConfigParam{
DataId: multipleClientsMultipleConfigsKey,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
_ = client.ListenConfig(listenConfigParam1)
err = client.CancelListenConfig(listenConfigParam)
assert.Nil(t, err)
assert.Equal(t, true, success)
select {
case c := <-ch:
assert.Equal(t, localConfigTest.Content, c)
case <-time.After(10 * time.Second):
assert.Errorf(t, errors.New("timeout"), "timeout")
}
})
}
func TestGetConfigWithSpecialSymbol(t *testing.T) {
contentStr := "hello world!!@#$%^&&*()"
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: "",
}
return ramContext
}
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",
}
client, _ := NewConfigClientWithRamCredentialProvider(&nc, provider)
client.configProxy = &MockConfigProxy{}
success, err := client.PublishConfig(vo.ConfigParam{
DataId: specialSymbolKey,
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: contentStr})
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: specialSymbolKey,
DataId: localConfigTest.DataId,
Group: localConfigTest.Group})
assert.Nil(t, err)
assert.Equal(t, contentStr, content)
}
type mockLogger struct {
}
func (m mockLogger) Info(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Warn(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Error(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Debug(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Infof(format string, args ...interface{}) {
logrus.Infof(format, args...)
}
func (m mockLogger) Warnf(format string, args ...interface{}) {
logrus.Warnf(format, args...)
}
func (m mockLogger) Errorf(format string, args ...interface{}) {
logrus.Errorf(format, args...)
}
func (m mockLogger) Debugf(format string, args ...interface{}) {
logrus.Debugf("implement me")
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

@ -17,19 +17,26 @@
package config_client
import (
"context"
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"time"
"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_server"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"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 {
@ -37,34 +44,37 @@ type ConfigProxy struct {
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, clientConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint)
proxy.nacosServer, err = nacos_server.NewNacosServerWithRamCredentialProvider(ctx, serverConfig, clientConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint, nil, provider)
proxy.clientConfig = clientConfig
return proxy, err
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) {
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, cp.clientConfig.TimeoutMs)
return result, err
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) {
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
@ -76,111 +86,147 @@ func (cp *ConfigProxy) SearchConfigProxy(param vo.SearchConfigParam, tenant, acc
params["dataId"] = ""
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
var version = "v2"
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
if err != nil {
return nil, err
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"
}
var configPage model.ConfigPage
err = json.Unmarshal([]byte(result), &configPage)
if version == "v2" {
err = json.Unmarshal([]byte(result), &configPage)
} else {
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) PublishConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, 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.MethodPost, cp.clientConfig.TimeoutMs)
func (cp *ConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
if group == "" {
group = constant.DEFAULT_GROUP
}
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.PublishConfig] publish config failed:" + err.Error())
return nil, err
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
} else {
return false, errors.New("[client.PublishConfig] publish config failed:" + 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 (cp *ConfigProxy) PublishAggProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
func appName(client *ConfigClient) string {
if clientConfig, err := client.GetClientConfig(); err == nil {
appName := clientConfig.AppName
return appName
}
params["method"] = "addDatum"
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
_, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_AGG_PATH, params, headers, http.MethodPost, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.PublishAggProxy] publish agg failed:" + err.Error())
}
return true, nil
return "unknown"
}
func (cp *ConfigProxy) DeleteAggProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
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,
}
params["method"] = "deleteDatum"
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
_, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_AGG_PATH, params, headers, http.MethodPost, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.DeleteAggProxy] delete agg failed:" + err.Error())
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 true, nil
return rpcClient
}
func (cp *ConfigProxy) DeleteConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, 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.MethodDelete, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.DeleteConfig] deleted config failed:" + err.Error())
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
} else {
return false, errors.New("[client.DeleteConfig] deleted config failed: " + string(result))
}
func (cp *ConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient {
return cp.createRpcClient(client.ctx, "0", client)
}
func (cp *ConfigProxy) ListenConfig(params map[string]string, isInitializing bool, tenant, accessKey, secretKey string) (string, error) {
//fixed at 30000msavoid frequent request on the server
var listenInterval uint64 = 30000
headers := map[string]string{
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
"Long-Pulling-Timeout": strconv.FormatUint(listenInterval, 10),
}
if isInitializing {
headers["Long-Pulling-Timeout-No-Hangup"] = "true"
}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
if len(tenant) > 0 {
params["tenant"] = tenant
}
logger.Infof("[client.ListenConfig] request params:%+v header:%+v \n", params, headers)
// In order to prevent the server from handling the delay of the client's long task,
// increase the client's read timeout to avoid this problem.
timeout := listenInterval + listenInterval/10
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_LISTEN_PATH, params, headers, http.MethodPost, timeout)
return result, err
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

@ -17,14 +17,14 @@
package nacos_client
import (
"errors"
"os"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/file"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"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 {
@ -35,7 +35,7 @@ type NacosClient struct {
serverConfigs []constant.ServerConfig
}
//SetClientConfig is use to set nacos client Config
// SetClientConfig is use to set nacos client Config
func (client *NacosClient) SetClientConfig(config constant.ClientConfig) (err error) {
if config.TimeoutMs <= 0 {
config.TimeoutMs = 10 * 1000
@ -61,27 +61,13 @@ func (client *NacosClient) SetClientConfig(config constant.ClientConfig) (err er
config.LogDir = file.GetCurrentPath() + string(os.PathSeparator) + "log"
}
if config.LogSampling != nil {
if config.LogSampling.Initial < 0 {
config.LogSampling.Initial = 100
}
if config.LogSampling.Thereafter < 0 {
config.LogSampling.Thereafter = 100
}
if config.LogSampling.Tick < 0 {
config.LogSampling.Tick = 10 * time.Second
}
}
client.clientConfig = config
client.clientConfigValid = true
return
}
//SetServerConfig is use to set nacos server config
// 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
@ -106,7 +92,7 @@ func (client *NacosClient) SetServerConfig(configs []constant.ServerConfig) (err
return
}
//GetClientConfig use to get client config
// GetClientConfig use to get client config
func (client *NacosClient) GetClientConfig() (config constant.ClientConfig, err error) {
config = client.clientConfig
if !client.clientConfigValid {
@ -115,7 +101,7 @@ func (client *NacosClient) GetClientConfig() (config constant.ClientConfig, err
return
}
//GetServerConfig use to get server config
// GetServerConfig use to get server config
func (client *NacosClient) GetServerConfig() (configs []constant.ServerConfig, err error) {
configs = client.serverConfigs
if !client.serverConfigsValid {
@ -124,7 +110,7 @@ func (client *NacosClient) GetServerConfig() (configs []constant.ServerConfig, e
return
}
//SetHttpAgent use to set http agent
// 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")
@ -134,7 +120,7 @@ func (client *NacosClient) SetHttpAgent(agent http_agent.IHttpAgent) (err error)
return
}
//GetHttpAgent use to get http agent
// 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

@ -17,8 +17,8 @@
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"
)
//go:generate mockgen -destination mock_nacos_client_interface.go -package nacos_client -source=./nacos_client_interface.go

View File

@ -1,228 +0,0 @@
/*
* 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 (
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
)
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 := NewPushReceiver(&hr)
hr.pushReceiver = *pr
if !notLoadCacheAtStart {
hr.loadCacheFromDisk()
}
go hr.asyncUpdateService()
return hr
}
func (hr *HostReactor) loadCacheFromDisk() {
serviceMap := cache.ReadServicesFromFile(hr.cacheDir)
if len(serviceMap) == 0 {
return
}
for k, v := range serviceMap {
hr.serviceInfoMap.Set(k, v)
}
}
func (hr *HostReactor) ProcessServiceJson(result string) {
service := util.JsonToService(result)
if service == nil {
return
}
cacheKey := util.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 service.Hosts == nil || len(service.Hosts) == 0 {
logger.Errorf("do not have useful host, ignore it, name:%s", service.Name)
return
}
}
hr.updateTimeMap.Set(cacheKey, uint64(util.CurrentMillis()))
hr.serviceInfoMap.Set(cacheKey, *service)
oldService, serviceOk := oldDomain.(model.Service)
if !ok || ok && serviceOk && isServiceInstanceChanged(&oldService, service) {
if !ok {
logger.Info("service not found in cache " + cacheKey)
} else {
logger.Info("service key:%s was updated to:%s", cacheKey, util.ToJsonString(service))
}
cache.WriteServicesToFile(*service, hr.cacheDir)
hr.subCallback.ServiceChanged(service)
}
}
func (hr *HostReactor) GetServiceInfo(serviceName string, clusters string) (model.Service, error) {
key := util.GetServiceCacheKey(serviceName, clusters)
cacheService, ok := hr.serviceInfoMap.Get(key)
if !ok {
hr.updateServiceNow(serviceName, clusters)
if cacheService, ok = hr.serviceInfoMap.Get(key); !ok {
return model.Service{}, errors.New("get service info failed")
}
}
return cacheService.(model.Service), nil
}
func (hr *HostReactor) GetAllServiceInfo(nameSpace, groupName string, pageNo, pageSize uint32) model.ServiceList {
data := model.ServiceList{}
result, err := hr.serviceProxy.GetAllServiceInfoList(nameSpace, groupName, pageNo, pageSize)
if err != nil {
logger.Errorf("GetAllServiceInfoList return error!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d err:%+v",
nameSpace, groupName, pageNo, pageSize, err)
return data
}
if result == "" {
logger.Errorf("GetAllServiceInfoList result is empty!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d",
nameSpace, groupName, pageNo, pageSize)
return data
}
err = json.Unmarshal([]byte(result), &data)
if err != nil {
logger.Errorf("GetAllServiceInfoList result json.Unmarshal error!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d",
nameSpace, groupName, pageNo, pageSize)
return data
}
return data
}
func (hr *HostReactor) updateServiceNow(serviceName, clusters string) {
result, err := hr.serviceProxy.QueryList(serviceName, clusters, hr.pushReceiver.port, false)
if err != nil {
logger.Errorf("QueryList return error!serviceName:%s cluster:%s err:%+v", serviceName, clusters, err)
return
}
if result == "" {
logger.Errorf("QueryList result is empty!serviceName:%s cluster:%s", serviceName, clusters)
return
}
hr.ProcessServiceJson(result)
}
func (hr *HostReactor) asyncUpdateService() {
sema := util.NewSemaphore(hr.updateThreadNum)
for {
for _, v := range hr.serviceInfoMap.Items() {
service := v.(model.Service)
lastRefTime, ok := hr.updateTimeMap.Get(util.GetServiceCacheKey(service.Name, service.Clusters))
if !ok {
lastRefTime = uint64(0)
}
if uint64(util.CurrentMillis())-lastRefTime.(uint64) > service.CacheMillis {
sema.Acquire()
go func() {
hr.updateServiceNow(service.Name, service.Clusters)
sema.Release()
}()
}
}
time.Sleep(1 * time.Second)
}
}
// 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.Warn(fmt.Sprintf("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,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

@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
package naming_cache
import (
"fmt"
@ -22,69 +21,16 @@ import (
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"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/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"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 TestHostReactor_GetServiceInfo(t *testing.T) {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewNamingClient(&nc)
param := vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "test",
Weight: 10,
ClusterName: "test",
Enable: true,
Healthy: true,
Ephemeral: true,
}
if param.GroupName == "" {
param.GroupName = constant.DEFAULT_GROUP
}
param.ServiceName = util.GetGroupName(param.ServiceName, param.GroupName)
_, _ = client.RegisterInstance(param)
_, err := client.hostReactor.GetServiceInfo(param.ServiceName, param.ClusterName)
assert.Nil(t, err)
}
func TestHostReactor_GetServiceInfoErr(t *testing.T) {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewNamingClient(&nc)
param := vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "test",
Weight: 10,
ClusterName: "test",
Enable: true,
Healthy: true,
Ephemeral: true,
}
_, _ = client.RegisterInstance(param)
_, err := client.hostReactor.GetServiceInfo(param.ServiceName, param.ClusterName)
assert.NotNil(t, err)
}
func TestHostReactor_isServiceInstanceChanged(t *testing.T) {
func TestServiceInfoHolder_isServiceInstanceChanged(t *testing.T) {
rand.Seed(time.Now().Unix())
defaultIp := createRandomIp()
defaultPort := creatRandomPort()
serviceA := &model.Service{
serviceA := model.Service{
LastRefTime: 1000,
Hosts: []model.Instance{
{
@ -101,7 +47,7 @@ func TestHostReactor_isServiceInstanceChanged(t *testing.T) {
},
},
}
serviceB := &model.Service{
serviceB := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
@ -119,7 +65,7 @@ func TestHostReactor_isServiceInstanceChanged(t *testing.T) {
},
}
ip := createRandomIp()
serviceC := &model.Service{
serviceC := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
@ -155,7 +101,7 @@ func TestHostReactor_isServiceInstanceChanged(t *testing.T) {
func TestHostReactor_isServiceInstanceChangedWithUnOrdered(t *testing.T) {
rand.Seed(time.Now().Unix())
serviceA := &model.Service{
serviceA := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
@ -173,7 +119,7 @@ func TestHostReactor_isServiceInstanceChangedWithUnOrdered(t *testing.T) {
},
}
serviceB := &model.Service{
serviceB := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{

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

@ -17,83 +17,91 @@
package naming_client
import (
"context"
"math"
"math/rand"
"os"
"sort"
"strings"
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/pkg/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/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"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
NamespaceId string
ctx context.Context
cancel context.CancelFunc
serviceProxy naming_proxy.INamingProxy
serviceInfoHolder *naming_cache.ServiceInfoHolder
isClosed bool
mutex sync.Mutex
}
type Chooser struct {
data []model.Instance
totals []int
max int
// NewNamingClient ...
func NewNamingClient(nc nacos_client.INacosClient) (*NamingClient, error) {
return NewNamingClientWithRamCredentialProvider(nc, nil)
}
func NewNamingClient(nc nacos_client.INacosClient) (NamingClient, error) {
// 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}
naming := &NamingClient{INacosClient: nc, ctx: ctx, cancel: cancel}
clientConfig, err := nc.GetClientConfig()
if err != nil {
return naming, err
}
naming.NamespaceId = clientConfig.NamespaceId
serverConfig, err := nc.GetServerConfig()
if err != nil {
return naming, err
}
httpAgent, err := nc.GetHttpAgent()
if err != nil {
return naming, err
}
loggerConfig := logger.Config{
LogFileName: constant.LOG_FILE_NAME,
Level: clientConfig.LogLevel,
Sampling: clientConfig.LogSampling,
LogRollingConfig: clientConfig.LogRollingConfig,
LogDir: clientConfig.LogDir,
CustomLogger: clientConfig.CustomLogger,
LogStdout: clientConfig.LogStdout,
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()
}
err = logger.InitLogger(loggerConfig)
if err != nil {
return naming, err
}
logger.GetLogger().Infof("logDir:<%s> cacheDir:<%s>", clientConfig.LogDir, clientConfig.CacheDir)
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
}
//RegisterInstance register instance
func initLogger(clientConfig constant.ClientConfig) error {
return logger.InitLogger(logger.BuildLoggerConfig(clientConfig))
}
// RegisterInstance ...
func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool, error) {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
@ -114,127 +122,157 @@ func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
}
beatInfo := &model.BeatInfo{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ServiceName: util.GetGroupName(param.ServiceName, param.GroupName),
Cluster: param.ClusterName,
Weight: param.Weight,
Period: util.GetDurationWithDefault(param.Metadata, constant.HEART_BEAT_INTERVAL, time.Second*5),
State: model.StateRunning,
}
_, err := sc.serviceProxy.RegisterInstance(util.GetGroupName(param.ServiceName, param.GroupName), param.GroupName, instance)
if err != nil {
return false, err
}
if instance.Ephemeral {
sc.beatReactor.AddBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), beatInfo)
}
return true, nil
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
}
//DeregisterInstance deregister 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
}
sc.beatReactor.RemoveBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port)
_, err := sc.serviceProxy.DeregisterInstance(util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port, param.Cluster, param.Ephemeral)
if err != nil {
return false, err
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
ClusterName: param.Cluster,
Ephemeral: param.Ephemeral,
}
return true, nil
return sc.serviceProxy.DeregisterInstance(param.ServiceName, param.GroupName, instance)
}
//UpdateInstance update information for exist 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.Ephemeral {
// Update the heartbeat information first to prevent the information
// from being flushed back to the original information after reconnecting
sc.beatReactor.RemoveBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port)
beatInfo := &model.BeatInfo{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ServiceName: util.GetGroupName(param.ServiceName, param.GroupName),
Cluster: param.ClusterName,
Weight: param.Weight,
Period: util.GetDurationWithDefault(param.Metadata, constant.HEART_BEAT_INTERVAL, time.Second*5),
State: model.StateRunning,
}
sc.beatReactor.AddBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), beatInfo)
if param.Metadata == nil {
param.Metadata = make(map[string]string)
}
instance := 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,
}
// Do update instance
_, err := sc.serviceProxy.UpdateInstance(
util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port, param.ClusterName, param.Ephemeral,
param.Weight, param.Enable, param.Metadata)
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
if err != nil {
return false, err
}
return true, nil
}
//GetService get service info
func (sc *NamingClient) GetService(param vo.GetServiceParam) (model.Service, error) {
// 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
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
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, "")
}
service.Clusters = clusters
service.Hosts = clusterSelector.SelectInstance(&service)
return service, err
}
//GetAllServicesInfo get all services info
// 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
}
clientConfig, _ := sc.GetClientConfig()
if len(param.NameSpace) == 0 {
if len(sc.NamespaceId) == 0 {
if len(clientConfig.NamespaceId) == 0 {
param.NameSpace = constant.DEFAULT_NAMESPACE_ID
} else {
param.NameSpace = sc.NamespaceId
param.NameSpace = clientConfig.NamespaceId
}
}
if param.PageNo == 0 {
param.PageNo = 1
}
if param.PageSize == 0 {
param.PageSize = 10
}
services := sc.hostReactor.GetAllServiceInfo(param.NameSpace, param.GroupName, param.PageNo, param.PageSize)
return services, nil
services, err := sc.serviceProxy.GetServiceList(param.PageNo, param.PageSize, param.GroupName, param.NameSpace, &model.ExpressionSelector{})
return services, err
}
//SelectAllInstances select all instances
// SelectAllInstances Get all instance by DataId 和 Group
func (sc *NamingClient) SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
if err != nil || service.Hosts == nil || len(service.Hosts) == 0 {
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 []model.Instance{}, err
}
return service.Hosts, err
instances := clusterSelector.SelectInstance(&service)
if instances == nil || len(instances) == 0 {
return []model.Instance{}, err
}
return instances, err
}
//SelectInstances select instances
// SelectInstances Get all instance by DataId, Group and Health
func (sc *NamingClient) SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
if err != nil {
return nil, err
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)
}
@ -244,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)
@ -252,15 +291,25 @@ func (sc *NamingClient) selectInstances(service model.Service, healthy bool) ([]
return result, nil
}
//SelectOneHealthyInstance select one healthy instance
// SelectOneHealthyInstance Get one healthy instance by DataId and Group
func (sc *NamingClient) SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
if err != nil {
return nil, err
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)
}
@ -284,67 +333,49 @@ func (sc *NamingClient) selectOneHealthyInstances(service model.Service) (*model
return nil, errors.New("healthy instance list is empty!")
}
chooser := newChooser(result)
instance := chooser.pick()
instance := newChooser(result).pick()
return &instance, nil
}
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]
}
//Subscribe subscibe service
// Subscribe ...
func (sc *NamingClient) Subscribe(param *vo.SubscribeParam) error {
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(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
svc, err := sc.GetService(serviceParam)
if err != nil {
return err
}
if !sc.hostReactor.serviceProxy.clientConfig.NotLoadCacheAtStart {
sc.subCallback.ServiceChanged(&svc)
}
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
}
//Unsubscribe unsubscribe service
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) error {
sc.subCallback.RemoveCallbackFuncs(util.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

@ -13,42 +13,40 @@
* 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"
)
//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
//Tenant optional
//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 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)
//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)
// 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)
// UpdateInstance use to modify instance
// DeregisterInstance use to deregister instance
// Ip required
// Port required
// Tenant optional
@ -56,51 +54,67 @@ type INamingClient interface {
// 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 use to get service
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
GetService(param vo.GetServiceParam) (model.Service, error)
//SelectAllInstance return all instances,include healthy=false,enable=false,weight<=0
//ServiceName require
//Clusters optional,default:DEFAULT
//GroupName optional,default:DEFAULT_GROUP
// 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 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)
//SelectInstances 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 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 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 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 use to get all service info by page
// 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

@ -17,18 +17,15 @@
package naming_client
import (
"net/http"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"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/vo"
"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.NewClientConfig(
@ -37,39 +34,60 @@ var clientConfigTest = *constant.NewClientConfig(
constant.WithNotLoadCacheAtStart(true),
)
var serverConfigTest = *constant.NewServerConfig("console.nacos.io", 80, constant.WithContextPath("/nacos"))
var serverConfigTest = *constant.NewServerConfig("127.0.0.1", 80, constant.WithContextPath("/nacos"))
func Test_RegisterServiceInstance_withoutGroupName(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(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "DEFAULT_GROUP@@DEMO",
"groupName": "DEFAULT_GROUP",
"app": "",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
type MockNamingProxy struct {
unsubscribeCalled bool
unsubscribeParams []string // 记录调用参数
}
func (m *MockNamingProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return true, nil
}
func (m *MockNamingProxy) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
return true, 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.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,
@ -80,39 +98,8 @@ func Test_RegisterServiceInstance_withoutGroupName(t *testing.T) {
}
func Test_RegisterServiceInstance_withGroupName(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(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO2",
"groupName": "test_group",
"app": "",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).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{
ServiceName: "DEMO2",
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
@ -123,39 +110,8 @@ func Test_RegisterServiceInstance_withGroupName(t *testing.T) {
}
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(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO3",
"groupName": "test_group",
"app": "",
"clusterName": "test",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).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{
ServiceName: "DEMO3",
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
@ -165,198 +121,35 @@ func Test_RegisterServiceInstance_withCluster(t *testing.T) {
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(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO4",
"groupName": "test_group",
"app": "",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).Times(3).
Return(http_agent.FakeHttpResponse(401, `no security`), 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: "DEMO4",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: false,
})
assert.Equal(t, false, result)
assert.NotNil(t, err)
}
func TestNamingProxy_DeregisterService_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(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "DEFAULT_GROUP@@DEMO5",
"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{
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 TestNamingProxy_DeregisterService_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(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO6",
"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{
success, err := NewTestNamingClient().DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO6",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: true,
})
}
func Test_UpdateServiceInstance_withoutGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("PUT"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "DEFAULT_GROUP@@DEMO",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"metadata": "{}",
"ephemeral": "false",
})).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.UpdateInstance(vo.UpdateInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
Ephemeral: false,
Metadata: map[string]string{},
})
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func TestNamingProxy_DeregisterService_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(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO7",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"ephemeral": "true",
})).Times(3).
Return(http_agent.FakeHttpResponse(401, `no security`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
_, _ = client.DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO7",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: true,
})
}
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",
@ -368,8 +161,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
@ -381,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",
@ -394,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",
@ -407,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",
@ -421,61 +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)
LastRefTime: 1528787794594, Clusters: "a"}
instance1, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.Nil(t, err)
assert.NotNil(t, instance1)
instance2, err := client.selectOneHealthyInstances(services)
instance2, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.Nil(t, err)
assert.NotNil(t, 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)
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",
@ -487,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",
@ -500,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",
@ -513,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",
@ -526,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",
@ -540,33 +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)
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",
@ -578,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",
@ -591,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",
@ -604,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",
@ -617,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",
@ -631,87 +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)
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)
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) {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewNamingClient(&nc)
reslut, err := client.GetAllServicesInfo(vo.GetAllServiceInfoParam{
result, err := NewTestNamingClient().GetAllServicesInfo(vo.GetAllServiceInfoParam{
GroupName: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 20,
})
assert.NotNil(t, reslut.Doms)
assert.NotNil(t, result.Doms)
assert.Nil(t, err)
}
func TestNamingClient_selectOneHealthyInstanceResult(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
func BenchmarkNamingClient_SelectOneHealthyInstances(b *testing.B) {
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Ip: "127.0.0.1",
Weight: 1,
Enable: true,
Healthy: true,
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,
},
{
Ip: "127.0.0.2",
Weight: 9,
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,
},
}})
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
client, _ := NewNamingClient(&nc)
for i := 0; i < 10; i++ {
{
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

@ -14,45 +14,50 @@
* limitations under the License.
*/
package naming_client
package naming_http
import (
"context"
"fmt"
"net/http"
"strconv"
"sync"
"sync/atomic"
"time"
"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/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"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
serviceProxy NamingProxy
clientBeatInterval int64
nacosServer *nacos_server.NacosServer
beatThreadCount int
beatThreadSemaphore *semaphore.Weighted
beatRecordMap cache.ConcurrentMap
clientCfg constant.ClientConfig
mux *sync.Mutex
}
const DefaultBeatThreadNum = 20
var ctx = context.Background()
func NewBeatReactor(serviceProxy NamingProxy, clientBeatInterval int64) BeatReactor {
func NewBeatReactor(ctx context.Context, clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer) BeatReactor {
br := BeatReactor{}
if clientBeatInterval <= 0 {
clientBeatInterval = 5 * 1000
}
br.ctx = ctx
br.beatMap = cache.NewConcurrentMap()
br.serviceProxy = serviceProxy
br.clientBeatInterval = clientBeatInterval
br.nacosServer = nacosServer
br.clientCfg = clientCfg
br.beatThreadCount = DefaultBeatThreadNum
br.beatRecordMap = cache.NewConcurrentMap()
br.beatThreadSemaphore = semaphore.NewWeighted(int64(br.beatThreadCount))
@ -76,6 +81,7 @@ func (br *BeatReactor) AddBeatInfo(serviceName string, beatInfo *model.BeatInfo)
}
br.beatMap.Set(k, beatInfo)
beatInfo.Metadata = util.DeepCopyMap(beatInfo.Metadata)
monitor.GetDom2BeatSizeMonitor().Set(float64(br.beatMap.Count()))
go br.sendInstanceBeat(k, beatInfo)
}
@ -89,16 +95,16 @@ func (br *BeatReactor) RemoveBeatInfo(serviceName string, ip string, port uint64
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 {
err := br.beatThreadSemaphore.Acquire(ctx, 1)
if err != nil {
logger.Errorf("sendInstanceBeat failed to acquire semaphore: %v", err)
return
}
br.beatThreadSemaphore.Acquire(br.ctx, 1)
//如果当前实例注销,则进行停止心跳
if atomic.LoadInt32(&beatInfo.State) == int32(model.StateShutdown) {
logger.Infof("instance[%s] stop heartBeating", k)
@ -107,7 +113,7 @@ func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
}
//进行心跳通信
beatInterval, err := br.serviceProxy.SendBeat(beatInfo)
beatInterval, err := br.SendBeat(beatInfo)
if err != nil {
logger.Errorf("beat to server return error:%+v", err)
br.beatThreadSemaphore.Release(1)
@ -121,8 +127,34 @@ func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
br.beatRecordMap.Set(k, util.CurrentMillis())
br.beatThreadSemaphore.Release(1)
t := time.NewTimer(beatInfo.Period)
<-t.C
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

@ -14,21 +14,21 @@
* limitations under the License.
*/
package naming_client
package naming_http
import (
"context"
"testing"
"time"
"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/util"
"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(NamingProxy{nacosServer: &nacos_server.NacosServer{}}, 5000)
br := NewBeatReactor(context.Background(), constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo := &model.BeatInfo{
@ -38,7 +38,6 @@ func TestBeatReactor_AddBeatInfo(t *testing.T) {
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
Period: time.Second * 5,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo)
key := buildKey(util.GetGroupName(serviceName, groupName), beatInfo.Ip, beatInfo.Port)
@ -48,7 +47,7 @@ func TestBeatReactor_AddBeatInfo(t *testing.T) {
}
func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
br := NewBeatReactor(NamingProxy{nacosServer: &nacos_server.NacosServer{}}, 5000)
br := NewBeatReactor(context.Background(), constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo1 := &model.BeatInfo{
@ -58,7 +57,6 @@ func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
Period: time.Second * 5,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo1)
beatInfo2 := &model.BeatInfo{
@ -68,7 +66,6 @@ func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
Period: time.Second * 5,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo2)
br.RemoveBeatInfo(util.GetGroupName(serviceName, groupName), "127.0.0.1", 8080)

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

@ -14,26 +14,29 @@
* limitations under the License.
*/
package naming_client
package naming_http
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"io/ioutil"
"io"
"math/rand"
"net"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/util"
"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 {
port int
host string
hostReactor *HostReactor
ctx context.Context
port int
host string
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
type PushData struct {
@ -46,11 +49,11 @@ var (
GZIP_MAGIC = []byte("\x1F\x8B")
)
func NewPushReceiver(hostReactor *HostReactor) *PushReceiver {
func NewPushReceiver(ctx context.Context, serviceInfoHolder *naming_cache.ServiceInfoHolder) *PushReceiver {
pr := PushReceiver{
hostReactor: hostReactor,
ctx: ctx,
serviceInfoHolder: serviceInfoHolder,
}
pr.startServer()
return &pr
}
@ -70,47 +73,46 @@ func (us *PushReceiver) tryListen() (*net.UDPConn, bool) {
return conn, true
}
func (us *PushReceiver) getConn() *net.UDPConn {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
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()
conn, ok = us.tryListen()
if ok {
logger.Infof("udp server start, port: " + strconv.Itoa(port))
return conn
break
}
if !ok && i == 2 {
logger.Errorf("failed to start udp server after trying 3 times.")
}
}
return nil
}
func (us *PushReceiver) startServer() {
conn := us.getConn()
if conn == nil {
return
}
go func() {
defer conn.Close()
for {
us.handleClient(conn)
select {
case <-us.ctx.Done():
return
default:
us.handleClient(conn)
}
}
}()
}
func (us *PushReceiver) handleClient(conn *net.UDPConn) {
if conn == nil {
time.Sleep(time.Second * 5)
conn = us.getConn()
if conn == nil {
return
}
}
data := make([]byte, 4024)
n, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
@ -130,7 +132,7 @@ func (us *PushReceiver) handleClient(conn *net.UDPConn) {
ack := make(map[string]string)
if pushData.PushType == "dom" || pushData.PushType == "service" {
us.hostReactor.ProcessServiceJson(pushData.Data)
us.serviceInfoHolder.ProcessServiceJson(pushData.Data)
ack["type"] = "push-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
@ -139,7 +141,7 @@ func (us *PushReceiver) handleClient(conn *net.UDPConn) {
} else if pushData.PushType == "dump" {
ack["type"] = "dump-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = util.ToJsonString(us.hostReactor.serviceInfoMap)
ack["data"] = util.ToJsonString(us.serviceInfoHolder.ServiceInfoMap)
} else {
ack["type"] = "unknow-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
@ -166,7 +168,7 @@ func TryDecompressData(data []byte) string {
}
defer reader.Close()
bs, err := ioutil.ReadAll(reader)
bs, err := io.ReadAll(reader)
if err != nil {
logger.Errorf("failed to decompress gzip data,err:%+v", err)

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,205 +0,0 @@
/*
* 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 (
"errors"
"fmt"
"net/http"
"strconv"
"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/logger"
"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/util"
)
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, clientCfg, 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) {
logger.Infof("register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instance))
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)
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) {
logger.Infof("deregister instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
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) UpdateInstance(serviceName string, ip string, port uint64, clusterName string, ephemeral bool, weight float64, enable bool, metadata map[string]string) (string, error) {
logger.Infof("modify instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
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)
params["weight"] = strconv.FormatFloat(weight, 'f', -1, 64)
params["enable"] = strconv.FormatBool(enable)
params["metadata"] = util.ToJsonString(metadata)
return proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodPut)
}
func (proxy *NamingProxy) SendBeat(info *model.BeatInfo) (int64, error) {
logger.Infof("namespaceId:<%s> sending beat to server:<%s>",
proxy.clientConfig.NamespaceId, util.ToJsonString(info))
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = info.ServiceName
params["beat"] = util.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, fmt.Errorf("namespaceId:<%s> sending beat to server:<%s> get 'clientBeatInterval' from <%s> error:<%+v>", proxy.clientConfig.NamespaceId, util.ToJsonString(info), result, err)
} 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"] = util.ToJsonString(selector)
}
}
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, fmt.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'count' from <%s> error:<%+v>", proxy.clientConfig.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 nil, fmt.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'doms' from <%s> error:<%+v> ", proxy.clientConfig.NamespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err)
}
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 {
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
}
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["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"
return proxy.nacosServer.ReqApi(api, param, http.MethodGet)
}
func (proxy *NamingProxy) GetAllServiceInfoList(namespace, groupName string, pageNo, pageSize uint32) (string, error) {
param := make(map[string]string)
param["namespaceId"] = namespace
param["groupName"] = groupName
param["pageNo"] = strconv.Itoa(int(pageNo))
param["pageSize"] = strconv.Itoa(int(pageSize))
api := constant.SERVICE_INFO_PATH + "/list"
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

@ -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,97 +0,0 @@
/*
* 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 (
"errors"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
)
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)) {
logger.Info("adding " + serviceName + " with " + clusters + " to listener map")
key := util.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)) {
logger.Info("removing " + serviceName + " with " + clusters + " to listener map")
key := util.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 := util.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 {
subscribeService := model.SubscribeService{
Valid: host.Valid,
Port: host.Port,
Ip: host.Ip,
Metadata: host.Metadata,
ServiceName: host.ServiceName,
ClusterName: host.ClusterName,
Weight: host.Weight,
InstanceId: host.InstanceId,
Enable: host.Enable,
Healthy: host.Healthy,
}
subscribeServices = append(subscribeServices, subscribeService)
}
(*funcItem)(subscribeServices, nil)
}
}
}

View File

@ -1,176 +0,0 @@
/*
* 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 (
"log"
"strings"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/stretchr/testify/assert"
)
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) {
},
}
ed.AddCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
key := util.GetServiceCacheKey(util.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) {
},
}
ed.AddCallbackFuncs(util.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) {
},
}
ed.AddCallbackFuncs(util.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(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
key := util.GetServiceCacheKey(util.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", util.ToJsonString(services))
},
}
ed.AddCallbackFuncs(util.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", util.ToJsonString(services))
},
}
ed.AddCallbackFuncs(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
ed.ServiceChanged(&service)
}

View File

@ -20,9 +20,7 @@ import (
"os"
"time"
"github.com/nacos-group/nacos-sdk-go/common/file"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"gopkg.in/natefinch/lumberjack.v2"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
)
func NewClientConfig(opts ...ClientOption) *ClientConfig {
@ -48,13 +46,6 @@ func NewClientConfig(opts ...ClientOption) *ClientConfig {
// ClientOption ...
type ClientOption func(*ClientConfig)
// WithCustomLogger ...
func WithCustomLogger(logger logger.Logger) ClientOption {
return func(config *ClientConfig) {
config.CustomLogger = logger
}
}
// WithTimeoutMs ...
func WithTimeoutMs(timeoutMs uint64) ClientOption {
return func(config *ClientConfig) {
@ -62,6 +53,13 @@ func WithTimeoutMs(timeoutMs uint64) ClientOption {
}
}
// WithAppName ...
func WithAppName(appName string) ClientOption {
return func(config *ClientConfig) {
config.AppName = appName
}
}
// WithBeatInterval ...
func WithBeatInterval(beatInterval int64) ClientOption {
return func(config *ClientConfig) {
@ -83,6 +81,27 @@ func WithEndpoint(endpoint string) ClientOption {
}
}
// 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) {
@ -104,6 +123,12 @@ func WithSecretKey(secretKey string) ClientOption {
}
}
func WithRamConfig(ramConfig *RamConfig) ClientOption {
return func(config *ClientConfig) {
config.RamConfig = ramConfig
}
}
// WithOpenKMS ...
func WithOpenKMS(openKMS bool) ClientOption {
return func(config *ClientConfig) {
@ -111,6 +136,25 @@ func WithOpenKMS(openKMS bool) ClientOption {
}
}
// 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) {
@ -118,6 +162,13 @@ func WithCacheDir(cacheDir string) ClientOption {
}
}
// WithDisableUseSnapShot ...
func WithDisableUseSnapShot(disableUseSnapShot bool) ClientOption {
return func(config *ClientConfig) {
config.DisableUseSnapShot = disableUseSnapShot
}
}
// WithUpdateThreadNum ...
func WithUpdateThreadNum(updateThreadNum int) ClientOption {
return func(config *ClientConfig) {
@ -170,20 +221,26 @@ func WithLogLevel(logLevel string) ClientOption {
// WithLogSampling ...
func WithLogSampling(tick time.Duration, initial int, thereafter int) ClientOption {
return func(config *ClientConfig) {
config.LogSampling = &logger.SamplingConfig{Initial: initial, Thereafter: thereafter, Tick: tick}
config.LogSampling = &ClientLogSamplingConfig{initial, thereafter, tick}
}
}
// WithLogRollingConfig ...
func WithLogRollingConfig(rollingConfig *lumberjack.Logger) ClientOption {
func WithLogRollingConfig(rollingConfig *ClientLogRollingConfig) ClientOption {
return func(config *ClientConfig) {
config.LogRollingConfig = rollingConfig
}
}
// WithLogStdout ...
func WithLogStdout(logStdout bool) ClientOption {
func WithTLS(tlsCfg TLSConfig) ClientOption {
return func(config *ClientConfig) {
config.LogStdout = logStdout
tlsCfg.Appointed = true
config.TLSCfg = tlsCfg
}
}
func WithAppConnLabels(appConnLabels map[string]string) ClientOption {
return func(config *ClientConfig) {
config.AppConnLabels = appConnLabels
}
}

View File

@ -19,9 +19,8 @@ package constant
import (
"os"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/common/file"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/stretchr/testify/assert"
)
@ -49,7 +48,6 @@ func TestNewClientConfig(t *testing.T) {
assert.Equal(t, config.RegionId, "")
assert.Equal(t, config.AccessKey, "")
assert.Equal(t, config.SecretKey, "")
assert.Nil(t, config.LogSampling)
}
func TestNewClientConfigWithOptions(t *testing.T) {
@ -73,8 +71,6 @@ func TestNewClientConfigWithOptions(t *testing.T) {
WithNamespaceId("namespace_1"),
WithAccessKey("accessKey_1"),
WithSecretKey("secretKey_1"),
WithLogSampling(time.Second*10, 5, 10),
)
assert.Equal(t, config.TimeoutMs, uint64(20000))
@ -96,8 +92,4 @@ func TestNewClientConfigWithOptions(t *testing.T) {
assert.Equal(t, config.NamespaceId, "namespace_1")
assert.Equal(t, config.AccessKey, "accessKey_1")
assert.Equal(t, config.SecretKey, "secretKey_1")
assert.Equal(t, config.LogSampling.Tick, time.Second*10)
assert.Equal(t, config.LogSampling.Initial, 5)
assert.Equal(t, config.LogSampling.Thereafter, 10)
}

View File

@ -16,41 +16,119 @@
package constant
import (
"github.com/nacos-group/nacos-sdk-go/common/logger"
"gopkg.in/natefinch/lumberjack.v2"
)
import "time"
type ServerConfig struct {
Scheme string //the nacos server scheme
ContextPath string //the nacos server contextpath
IpAddr string //the nacos server address
Port uint64 //the nacos server port
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 // 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
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
OpenKMS bool // it's to open kms,default is false. https://help.aliyun.com/product/28933.html
CacheDir string // the directory for persist nacos service info,default value is current path
UpdateThreadNum int // the number of gorutine 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
LogSampling *logger.SamplingConfig // the sampling config of log
ContextPath string // the nacos server contextpath
LogRollingConfig *lumberjack.Logger // the log rolling config
CustomLogger logger.Logger // the custom log interface ,With a custom Logger (nacos sdk will not provide log cutting and archiving capabilities)
LogStdout bool // the stdout redirect for log, default is false
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

@ -16,66 +16,106 @@
package constant
import "time"
type KMSVersion string
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:v1.0.1"
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"
WINDOWS_LEGAL_NAME_SPLITER = "&&"
OS_WINDOWS = "windows"
LOG_FILE_NAME = "nacos-sdk.log"
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

@ -34,30 +34,37 @@ func NewServerConfig(ipAddr string, port uint64, opts ...ServerOption) *ServerCo
// ServerOption ...
type ServerOption func(*ServerConfig)
//WithScheme set Scheme for server
// WithScheme set Scheme for server
func WithScheme(scheme string) ServerOption {
return func(config *ServerConfig) {
config.Scheme = scheme
}
}
//WithContextPath set contextPath for server
// WithContextPath set contextPath for server
func WithContextPath(contextPath string) ServerOption {
return func(config *ServerConfig) {
config.ContextPath = contextPath
}
}
//WithIpAddr set ip address for server
// WithIpAddr set ip address for server
func WithIpAddr(ipAddr string) ServerOption {
return func(config *ServerConfig) {
config.IpAddr = ipAddr
}
}
//WithPort set port for server
// 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,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
}

View File

@ -21,6 +21,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
)
var osType string
@ -38,16 +39,51 @@ func init() {
}
func MkdirIfNecessary(createDir string) (err error) {
return os.MkdirAll(createDir, os.ModePerm)
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 := os.Getwd() //当前的目录
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
dir, err = filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Println("can not get current path")
}
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
}

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

@ -18,21 +18,23 @@ package http_agent
import (
"net/http"
"net/url"
"strings"
"time"
)
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

@ -18,23 +18,26 @@ package http_agent
import (
"net/http"
"net/url"
"strings"
"time"
)
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

@ -17,20 +17,28 @@
package http_agent
import (
"io/ioutil"
"io"
"net/http"
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/util"
"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"
)
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 {
@ -56,11 +64,11 @@ func (agent *HttpAgent) RequestOnlyResult(method string, path string, header htt
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 {
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 {
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],read error:%+v", method, path, util.ToJsonString(header), util.ToJsonString(params), errRead)
@ -92,13 +100,37 @@ func (agent *HttpAgent) Request(method string, path string, header http.Header,
}
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

@ -20,11 +20,10 @@ import (
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
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)
body := util.GetUrlFormedMap(params)

View File

@ -22,8 +22,7 @@ import (
"time"
)
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 {

View File

@ -21,6 +21,7 @@ import (
"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"
@ -40,12 +41,9 @@ var levelMap = map[string]zapcore.Level{
type Config struct {
Level string
LogFileName string
Sampling *SamplingConfig
AppendToStdout bool
LogRollingConfig *lumberjack.Logger
LogDir string
CustomLogger Logger
LogStdout bool
}
type SamplingConfig struct {
@ -86,35 +84,59 @@ func init() {
EncodeCaller: zapcore.ShortCallerEncoder,
}
zapLoggerConfig.EncoderConfig = zapLoggerEncoderConfig
defaultLogger, _ := zapLoggerConfig.Build(zap.AddCaller(), zap.AddCallerSkip(1))
setLogger(&NacosLogger{defaultLogger.Sugar()})
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) {
l, err := initNacosLogger(config)
if err != nil {
return err
}
setLogger(l)
logLock.Lock()
defer logLock.Unlock()
logger, err = InitNacosLogger(config)
return
}
// InitNacosLogger is init nacos default logger
func initNacosLogger(config Config) (Logger, error) {
if config.CustomLogger != nil {
return &NacosLogger{config.CustomLogger}, nil
}
func InitNacosLogger(config Config) (Logger, error) {
logLevel := getLogLevel(config.Level)
encoder := getEncoder()
writer := config.getLogWriter()
core := zapcore.NewCore(zapcore.NewConsoleEncoder(encoder), writer, logLevel)
if config.Sampling != nil {
core = zapcore.NewSamplerWithOptions(core, config.Sampling.Tick, config.Sampling.Initial, config.Sampling.Thereafter)
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
}
@ -134,15 +156,15 @@ func getEncoder() zapcore.EncoderConfig {
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
//SetLogger sets logger for sdk
func setLogger(log Logger) {
// SetLogger sets logger for sdk
func SetLogger(log Logger) {
logLock.Lock()
defer logLock.Unlock()
logger = log
@ -156,12 +178,5 @@ func GetLogger() Logger {
// getLogWriter get Lumberjack writer by LumberjackConfig
func (c *Config) getLogWriter() zapcore.WriteSyncer {
if c.LogRollingConfig == nil {
c.LogRollingConfig = &lumberjack.Logger{}
}
c.LogRollingConfig.Filename = c.LogDir + string(os.PathSeparator) + c.LogFileName
if c.LogStdout {
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(c.LogRollingConfig), zapcore.AddSync(os.Stdout))
}
return zapcore.AddSync(c.LogRollingConfig)
}

View File

@ -24,12 +24,12 @@ import (
)
func reset() {
setLogger(nil)
SetLogger(nil)
}
func TestInitLogger(t *testing.T) {
config := Config{
Level: "debug",
Level: "degug",
}
err := InitLogger(config)
assert.NoError(t, err)
@ -47,11 +47,6 @@ func TestGetLogger(t *testing.T) {
log2 := GetLogger()
assert.NotEqual(t, log, log2)
// the secend init logger
config.Level = "info"
_ = InitLogger(config)
log3 := GetLogger()
assert.NotEqual(t, log2, log3)
reset()
}
@ -59,20 +54,13 @@ func TestSetLogger(t *testing.T) {
// not yet init get default log
log := GetLogger()
log1 := &mockLogger{}
setLogger(log1)
SetLogger(log1)
// after set logger
log2 := GetLogger()
assert.NotEqual(t, log, log2)
assert.Equal(t, log1, log2)
config := Config{
Level: "degug",
}
_ = InitLogger(config)
// after init logger
log3 := GetLogger()
assert.NotEqual(t, log2, log3)
reset()
}
@ -82,7 +70,7 @@ func TestRaceLogger(t *testing.T) {
wg.Add(3)
go func() {
defer wg.Done()
setLogger(&mockLogger{})
SetLogger(&mockLogger{})
}()
go func() {
defer wg.Done()
@ -91,7 +79,7 @@ func TestRaceLogger(t *testing.T) {
go func() {
defer wg.Done()
config := Config{
Level: "debug",
Level: "degug",
}
_ = InitLogger(config)
}()

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

@ -19,7 +19,7 @@ package nacos_error
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
type NacosError struct {

View File

@ -17,76 +17,92 @@
package nacos_server
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"context"
"io"
"math/rand"
"net/http"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"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/security"
"github.com/nacos-group/nacos-sdk-go/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/util"
"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
securityLogin security.AuthClient
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
endpoint string
lastSrvRefTime int64
vipSrvRefInterMills int64
contextPath string
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, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string) (*NacosServer, error) {
if len(serverList) == 0 && endpoint == "" {
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.NewAuthClient(clientCfg, serverList, httpAgent)
securityLogin := security.NewSecurityProxyWithRamCredentialProvider(clientCfg, serverList, httpAgent, provider)
ns := NacosServer{
serverList: serverList,
securityLogin: securityLogin,
httpAgent: httpAgent,
timeoutMs: timeoutMs,
endpoint: endpoint,
vipSrvRefInterMills: 10000,
contextPath: clientCfg.ContextPath,
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()
_, err := securityLogin.Login()
if err != nil {
logger.Errorf("login has error %+v", err)
if severLen > 0 {
ns.currentIndex = rand.Int31n(int32(severLen))
} else {
ns.initRefreshSrvIfNeed(ctx)
}
securityLogin.AutoRefresh()
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, timeoutMS uint64) (result string, err error) {
start := time.Now()
if contextPath == "" {
contextPath = constant.WEB_CONTEXT
}
signHeaders := getSignHeaders(params, newHeaders)
url := curServer + contextPath + api
headers := map[string][]string{}
@ -105,26 +121,22 @@ func (server *NacosServer) callConfigServer(api string, params map[string]string
return
}
headers["RequestId"] = []string{uid.String()}
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
headers["Spas-AccessKey"] = []string{newHeaders["accessKey"]}
headers["Timestamp"] = []string{signHeaders["timeStamp"]}
headers["Spas-Signature"] = []string{signHeaders["Spas-Signature"]}
injectSecurityInfo(server, params)
var response *http.Response
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)
@ -133,6 +145,7 @@ 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
}
@ -152,24 +165,23 @@ func (server *NacosServer) callServer(api string, params map[string]string, meth
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
injectSecurityInfo(server, params)
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, server.timeoutMs, params)
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 {
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
}
}
@ -180,7 +192,7 @@ func (server *NacosServer) ReqConfigApi(api string, params map[string]string, he
return "", errors.New("server list is empty")
}
injectSecurityInfo(server, params)
server.InjectSecurityInfo(params, security.BuildConfigResource(params["tenant"], params["group"], params["dataId"]))
//only one server,retry request when error
var err error
@ -193,7 +205,6 @@ func (server *NacosServer) ReqConfigApi(api string, params map[string]string, he
}
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++ {
@ -205,22 +216,21 @@ func (server *NacosServer) ReqConfigApi(api string, params map[string]string, he
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")
}
var (
result string
err error
)
injectSecurityInfo(server, params)
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)
@ -241,33 +251,51 @@ func (server *NacosServer) ReqApi(api string, params map[string]string, method s
index = (index + i) % len(srvs)
}
}
return "", fmt.Errorf("retry%stimes request failed,err=%v", strconv.Itoa(constant.REQUEST_DOMAIN_RETRY_TIME), err)
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() {
for {
time.Sleep(time.Duration(1) * time.Second)
server.refreshServerSrvIfNeed()
select {
case <-ctx.Done():
return
default:
time.Sleep(time.Duration(10) * time.Second)
server.refreshServerSrvIfNeed(urlString, server.endpointQueryHeader)
}
}
}()
}
func (server *NacosServer) refreshServerSrvIfNeed() {
if len(server.serverList) > 0 || util.CurrentMillis()-server.lastSrvRefTime < server.vipSrvRefInterMills {
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")
logger.Infof("http nacos server list: <%s>", result)
var servers []constant.ServerConfig
contextPath := server.contextPath
@ -293,9 +321,15 @@ func (server *NacosServer) refreshServerSrvIfNeed() {
if len(servers) > 0 {
if !reflect.DeepEqual(server.serverList, servers) {
server.Lock()
logger.Infof("server list is updated, old: <%v>,new:<%v>", server.serverList, servers)
var serverPrev = server.serverList
logger.Infof("server list is updated, old: <%v>,new:<%v>", serverPrev, servers)
server.serverList = servers
if serverPrev != nil {
server.ServerSrcChangeSignal <- struct{}{}
}
server.lastSrvRefTime = util.CurrentMillis()
server.securityLogin.UpdateServerList(servers)
server.Unlock()
}
@ -307,10 +341,10 @@ func (server *NacosServer) GetServerList() []constant.ServerConfig {
return server.serverList
}
func injectSecurityInfo(server *NacosServer, param map[string]string) {
accessToken := server.securityLogin.GetAccessToken()
if accessToken != "" {
param[constant.KEY_ACCESS_TOKEN] = accessToken
func (server *NacosServer) InjectSecurityInfo(param map[string]string, resource security.RequestResource) {
securityInfo := server.securityLogin.GetSecurityInfo(resource)
for k, v := range securityInfo {
param[k] = v
}
}
@ -321,38 +355,11 @@ func getAddress(cfg constant.ServerConfig) string {
return cfg.Scheme + "://" + 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"]
func (server *NacosServer) GetNextServer() (constant.ServerConfig, error) {
serverLen := len(server.GetServerList())
if serverLen == 0 {
return constant.ServerConfig{}, errors.New("server is empty")
}
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"])
}
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

@ -17,9 +17,13 @@
package nacos_server
import (
"context"
"testing"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"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"
)
@ -46,3 +50,138 @@ func Test_getAddressWithoutScheme(t *testing.T) {
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

@ -0,0 +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 rpc
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())},
}
}

View File

@ -0,0 +1,637 @@
/*
* 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"
"fmt"
"math"
"os"
"reflect"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"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/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"
)
type ConnectionType uint32
const (
GRPC ConnectionType = iota
)
type RpcClientStatus int32
const (
INITIALIZED RpcClientStatus = iota
STARTING
UNHEALTHY
RUNNING
SHUTDOWN
)
func (status RpcClientStatus) getDesc() string {
switch status {
case INITIALIZED:
return "INITIALIZED"
case STARTING:
return "STARTING"
case UNHEALTHY:
return "UNHEALTHY"
case RUNNING:
return "RUNNING"
case SHUTDOWN:
return "SHUTDOWN"
default:
return "UNKNOWN"
}
}
type ConnectionStatus uint32
const (
DISCONNECTED ConnectionStatus = iota
CONNECTED
)
var (
cMux = new(sync.Mutex)
clientMap = make(map[string]IRpcClient)
)
type IRpcClient interface {
connectToServer(serverInfo ServerInfo) (IConnection, error)
getConnectionType() ConnectionType
putAllLabels(labels map[string]string)
rpcPortOffset() uint64
GetRpcClient() *RpcClient
}
type ServerInfo struct {
serverIp string
serverPort uint64
serverGrpcPort uint64
}
type RpcClient struct {
ctx context.Context
name string
labels map[string]string
currentConnection IConnection
rpcClientStatus RpcClientStatus
eventChan chan ConnectionEvent
reconnectionChan chan ReconnectContext
connectionEventListeners atomic.Value
lastActiveTimestamp atomic.Value
executeClient IRpcClient
nacosServer *nacos_server.NacosServer
serverRequestHandlerMapping sync.Map
mux *sync.Mutex
clientAbilities rpc_request.ClientAbilities
Tenant string
}
type ServerRequestHandlerMapping struct {
serverRequest func() rpc_request.IRequest
handler IServerRequestHandler
}
type ReconnectContext struct {
onRequestFail bool
serverInfo ServerInfo
}
type ConnectionEvent struct {
eventType ConnectionStatus
}
func (r *RpcClient) putAllLabels(labels map[string]string) {
for k, v := range labels {
r.labels[k] = v
}
}
func (r *RpcClient) GetRpcClient() *RpcClient {
return r
}
/**
* get all client.
*
*/
func getAllClient() map[string]IRpcClient {
return clientMap
}
func getClient(clientName string) IRpcClient {
return clientMap[clientName]
}
func CreateClient(ctx context.Context, clientName string, connectionType ConnectionType, labels map[string]string, nacosServer *nacos_server.NacosServer, tlsConfig *constant.TLSConfig, appConnLabels map[string]string) (IRpcClient, error) {
cMux.Lock()
defer cMux.Unlock()
if _, ok := clientMap[clientName]; !ok {
logger.Infof("init rpc client for name ", clientName)
var rpcClient IRpcClient
if GRPC == connectionType {
rpcClient = NewGrpcClient(ctx, clientName, nacosServer, tlsConfig)
}
if rpcClient == nil {
return nil, errors.New("unsupported connection type")
}
logger.Infof("get app conn labels from client config %v ", appConnLabels)
appConnLabelsEnv := getAppLabelsFromEnv()
logger.Infof("get app conn labels from env %v ", appConnLabelsEnv)
appConnLabelsFinal := mergerAppLabels(appConnLabels, appConnLabelsEnv)
logger.Infof("final app conn labels : %v ", appConnLabelsFinal)
appConnLabelsFinal = addPrefixForEachKey(appConnLabelsFinal, "app_")
if len(appConnLabelsFinal) != 0 {
rpcClient.putAllLabels(appConnLabelsFinal)
}
rpcClient.putAllLabels(labels)
clientMap[clientName] = rpcClient
return rpcClient, nil
}
return clientMap[clientName], nil
}
func mergerAppLabels(appLabelsAppointed map[string]string, appLabelsEnv map[string]string) map[string]string {
preferred := strings.ToLower(os.Getenv("nacos_app_conn_labels_preferred"))
var preferFirst bool
if preferred != "env" {
preferFirst = true
} else {
preferFirst = false
}
return mergeMaps(appLabelsAppointed, appLabelsEnv, preferFirst)
}
func mergeMaps(map1, map2 map[string]string, preferFirst bool) map[string]string {
result := make(map[string]string, 8)
for k, v := range map1 {
result[k] = v
}
for k, v := range map2 {
_, ok := map1[k]
if preferFirst && ok {
continue
}
result[k] = v
}
return result
}
func getAppLabelsFromEnv() map[string]string {
configMap := make(map[string]string, 8)
// nacos_config_gray_label
grayLabel := os.Getenv("nacos_config_gray_label")
if grayLabel != "" {
configMap["nacos_config_gray_label"] = grayLabel
}
// nacos_app_conn_labels
connLabels := os.Getenv("nacos_app_conn_labels")
if connLabels != "" {
labelsMap := parseLabels(connLabels)
for k, v := range labelsMap {
configMap[k] = v
}
}
return configMap
}
func parseLabels(rawLabels string) map[string]string {
if strings.TrimSpace(rawLabels) == "" {
return make(map[string]string, 2)
}
resultMap := make(map[string]string, 2)
labels := strings.Split(rawLabels, ",")
for _, label := range labels {
if strings.TrimSpace(label) != "" {
kv := strings.Split(label, "=")
if len(kv) == 2 {
resultMap[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
} else {
fmt.Println("unknown label format:", label)
}
}
}
return resultMap
}
func addPrefixForEachKey(m map[string]string, prefix string) map[string]string {
if len(m) == 0 {
return m
}
newMap := make(map[string]string, len(m))
for k, v := range m {
if strings.TrimSpace(k) != "" {
newKey := prefix + k
newMap[newKey] = v
}
}
return newMap
}
func (r *RpcClient) Start() {
if ok := atomic.CompareAndSwapInt32((*int32)(&r.rpcClientStatus), (int32)(INITIALIZED), (int32)(STARTING)); !ok {
return
}
r.registerServerRequestHandlers()
go func() {
for {
select {
case event := <-r.eventChan:
r.notifyConnectionEvent(event)
case <-r.ctx.Done():
return
}
}
}()
go func() {
timer := time.NewTimer(5 * time.Second)
for {
select {
case rc := <-r.reconnectionChan:
if (rc.serverInfo != ServerInfo{}) {
var serverExist bool
for _, v := range r.nacosServer.GetServerList() {
if rc.serverInfo.serverIp == v.IpAddr {
rc.serverInfo.serverPort = v.Port
rc.serverInfo.serverGrpcPort = v.GrpcPort
serverExist = true
break
}
}
if !serverExist {
logger.Infof("%s recommend server is not in server list, ignore recommend server %+v", r.name, rc.serverInfo)
rc.serverInfo = ServerInfo{}
}
}
r.reconnect(rc.serverInfo, rc.onRequestFail)
case <-timer.C:
r.healthCheck(timer)
case <-r.nacosServer.ServerSrcChangeSignal:
r.notifyServerSrvChange()
case <-r.ctx.Done():
return
}
}
}()
var currentConnection IConnection
startUpRetryTimes := constant.REQUEST_DOMAIN_RETRY_TIME
for startUpRetryTimes > 0 && currentConnection == nil {
startUpRetryTimes--
serverInfo, err := r.nextRpcServer()
if err != nil {
logger.Errorf("[RpcClient.nextRpcServer],err:%v", err)
break
}
logger.Infof("[RpcClient.Start] %s try to connect to server on start up, server: %+v", r.name, serverInfo)
if connection, err := r.executeClient.connectToServer(serverInfo); err != nil {
logger.Warnf("[RpcClient.Start] %s fail to connect to server on start up, error message=%v, "+
"start up retry times left=%d", r.name, err.Error(), startUpRetryTimes)
} else {
currentConnection = connection
break
}
}
if currentConnection != nil {
logger.Infof("%s success to connect to server %+v on start up, connectionId=%s", r.name,
currentConnection.getServerInfo(), currentConnection.getConnectionId())
r.currentConnection = currentConnection
atomic.StoreInt32((*int32)(&r.rpcClientStatus), (int32)(RUNNING))
r.notifyConnectionChange(CONNECTED)
} else {
r.switchServerAsync(ServerInfo{}, false)
}
}
func (r *RpcClient) notifyConnectionChange(eventType ConnectionStatus) {
r.eventChan <- ConnectionEvent{eventType: eventType}
}
func (r *RpcClient) notifyServerSrvChange() {
if r.currentConnection == nil {
r.switchServerAsync(ServerInfo{}, false)
return
}
curServerInfo := r.currentConnection.getServerInfo()
var found bool
for _, ele := range r.nacosServer.GetServerList() {
if ele.IpAddr == curServerInfo.serverIp {
found = true
}
}
if !found {
logger.Infof("Current connected server %s:%d is not in latest server list, switch switchServerAsync", curServerInfo.serverIp, curServerInfo.serverPort)
r.switchServerAsync(ServerInfo{}, false)
}
}
func (r *RpcClient) registerServerRequestHandlers() {
// register ConnectResetRequestHandler.
r.RegisterServerRequestHandler(func() rpc_request.IRequest {
return &rpc_request.ConnectResetRequest{InternalRequest: rpc_request.NewInternalRequest()}
}, &ConnectResetRequestHandler{})
// register client detection request.
r.RegisterServerRequestHandler(func() rpc_request.IRequest {
return &rpc_request.ClientDetectionRequest{InternalRequest: rpc_request.NewInternalRequest()}
}, &ClientDetectionRequestHandler{})
}
func (r *RpcClient) Shutdown() {
atomic.StoreInt32((*int32)(&r.rpcClientStatus), (int32)(SHUTDOWN))
r.closeConnection()
}
func (r *RpcClient) RegisterServerRequestHandler(request func() rpc_request.IRequest, handler IServerRequestHandler) {
requestType := request().GetRequestType()
if handler == nil || requestType == "" {
logger.Errorf("%s register server push request handler "+
"missing required parameters,request:%+v handler:%+v", r.name, requestType, handler.Name())
return
}
logger.Debugf("%s register server push request:%s handler:%+v", r.name, requestType, handler.Name())
r.serverRequestHandlerMapping.Store(requestType, ServerRequestHandlerMapping{
serverRequest: request,
handler: handler,
})
}
func (r *RpcClient) RegisterConnectionListener(listener IConnectionEventListener) {
logger.Debugf("%s register connection listener [%+v] to current client", r.name, reflect.TypeOf(listener))
listeners := r.connectionEventListeners.Load()
connectionEventListeners := listeners.([]IConnectionEventListener)
connectionEventListeners = append(connectionEventListeners, listener)
r.connectionEventListeners.Store(connectionEventListeners)
}
func (r *RpcClient) switchServerAsync(recommendServerInfo ServerInfo, onRequestFail bool) {
r.reconnectionChan <- ReconnectContext{serverInfo: recommendServerInfo, onRequestFail: onRequestFail}
}
func (r *RpcClient) reconnect(serverInfo ServerInfo, onRequestFail bool) {
if onRequestFail && r.sendHealthCheck() {
logger.Infof("%s server check success, currentServer is %+v", r.name, r.currentConnection.getServerInfo())
atomic.StoreInt32((*int32)(&r.rpcClientStatus), (int32)(RUNNING))
r.notifyConnectionChange(CONNECTED)
return
}
var (
serverInfoFlag bool
reConnectTimes, retryTurns int
err error
)
if (serverInfo == ServerInfo{}) {
serverInfoFlag = true
logger.Infof("%s try to re connect to a new server, server is not appointed, will choose a random server.", r.name)
}
for !r.isShutdown() {
if serverInfoFlag {
serverInfo, err = r.nextRpcServer()
if err != nil {
logger.Errorf("[RpcClient.nextRpcServer],err:%v", err)
break
}
}
connectionNew, err := r.executeClient.connectToServer(serverInfo)
if connectionNew != nil && err == nil {
logger.Infof("%s success to connect a server %+v, connectionId=%s", r.name, serverInfo,
connectionNew.getConnectionId())
if r.currentConnection != nil {
logger.Infof("%s abandon prev connection, server is %+v, connectionId is %s", r.name, serverInfo,
r.currentConnection.getConnectionId())
r.currentConnection.setAbandon(true)
r.closeConnection()
}
r.currentConnection = connectionNew
atomic.StoreInt32((*int32)(&r.rpcClientStatus), (int32)(RUNNING))
r.notifyConnectionChange(CONNECTED)
return
}
if r.isShutdown() {
r.closeConnection()
}
if reConnectTimes > 0 && reConnectTimes%len(r.nacosServer.GetServerList()) == 0 {
logger.Warnf("%s fail to connect server, after trying %d times, last try server is %+v, error=%v", r.name,
reConnectTimes, serverInfo, err)
if retryTurns < 50 {
retryTurns++
}
}
reConnectTimes++
if !r.IsRunning() {
time.Sleep(time.Duration((math.Min(float64(retryTurns), 50))*100) * time.Millisecond)
}
}
if r.isShutdown() {
logger.Warnf("%s client is shutdown, stop reconnect to server", r.name)
}
}
func (r *RpcClient) closeConnection() {
if r.currentConnection != nil {
r.currentConnection.close()
r.notifyConnectionChange(DISCONNECTED)
}
}
// Notify when client new connected.
func (r *RpcClient) notifyConnectionEvent(event ConnectionEvent) {
listeners := r.connectionEventListeners.Load().([]IConnectionEventListener)
if len(listeners) == 0 {
return
}
logger.Infof("%s notify %s event to listeners , connectionId=%s", r.name, event.toString(), r.currentConnection.getConnectionId())
for _, v := range listeners {
if event.isConnected() {
v.OnConnected()
}
if event.isDisConnected() {
v.OnDisConnect()
}
}
}
func (r *RpcClient) healthCheck(timer *time.Timer) {
defer timer.Reset(constant.KEEP_ALIVE_TIME * time.Second)
var reconnectContext ReconnectContext
lastActiveTimeStamp := r.lastActiveTimestamp.Load().(time.Time)
if time.Now().Sub(lastActiveTimeStamp) < constant.KEEP_ALIVE_TIME*time.Second {
return
}
if r.sendHealthCheck() {
r.lastActiveTimestamp.Store(time.Now())
return
} else {
if r.currentConnection == nil || r.isShutdown() {
return
}
logger.Infof("%s server healthy check fail, currentConnection=%s", r.name, r.currentConnection.getConnectionId())
atomic.StoreInt32((*int32)(&r.rpcClientStatus), (int32)(UNHEALTHY))
reconnectContext = ReconnectContext{onRequestFail: false}
}
r.reconnect(reconnectContext.serverInfo, reconnectContext.onRequestFail)
}
func (r *RpcClient) sendHealthCheck() bool {
if r.currentConnection == nil {
return false
}
response, err := r.currentConnection.request(rpc_request.NewHealthCheckRequest(),
constant.DEFAULT_TIMEOUT_MILLS, r)
if err != nil {
logger.Errorf("client sendHealthCheck failed,err=%v", err)
return false
}
if !response.IsSuccess() {
// when client request immediately after the nacos server starts, the server may not ready to serve new request
// the server will return code 3xx, tell the client to retry after a while
// this situation, just return true,because the healthCheck will start again after 5 seconds
if response.GetErrorCode() >= 300 && response.GetErrorCode() < 400 {
return true
}
return false
}
return true
}
func (r *RpcClient) nextRpcServer() (ServerInfo, error) {
serverConfig, err := r.nacosServer.GetNextServer()
if err != nil {
return ServerInfo{}, err
}
return ServerInfo{
serverIp: serverConfig.IpAddr,
serverPort: serverConfig.Port,
serverGrpcPort: serverConfig.GrpcPort,
}, nil
}
func (c *ConnectionEvent) isConnected() bool {
return c.eventType == CONNECTED
}
func (c *ConnectionEvent) isDisConnected() bool {
return c.eventType == DISCONNECTED
}
// check is this client is shutdown.
func (r *RpcClient) isShutdown() bool {
return atomic.LoadInt32((*int32)(&r.rpcClientStatus)) == (int32)(SHUTDOWN)
}
// IsRunning check is this client is running.
func (r *RpcClient) IsRunning() bool {
return atomic.LoadInt32((*int32)(&r.rpcClientStatus)) == (int32)(RUNNING)
}
func (r *RpcClient) IsInitialized() bool {
return atomic.LoadInt32((*int32)(&r.rpcClientStatus)) == (int32)(INITIALIZED)
}
func (c *ConnectionEvent) toString() string {
if c.isConnected() {
return "connected"
}
if c.isDisConnected() {
return "disconnected"
}
return ""
}
func (r *RpcClient) Request(request rpc_request.IRequest, timeoutMills int64) (rpc_response.IResponse, error) {
retryTimes := 0
start := util.CurrentMillis()
var currentErr error
for retryTimes < constant.REQUEST_DOMAIN_RETRY_TIME && util.CurrentMillis() < start+timeoutMills {
if r.currentConnection == nil || !r.IsRunning() {
currentErr = waitReconnect(timeoutMills, &retryTimes, request,
errors.Errorf("client not connected, current status:%s", r.rpcClientStatus.getDesc()))
continue
}
response, err := r.currentConnection.request(request, timeoutMills, r)
if err != nil {
currentErr = waitReconnect(timeoutMills, &retryTimes, request, err)
continue
}
if resp, ok := response.(*rpc_response.ErrorResponse); ok {
if resp.GetErrorCode() == constant.UN_REGISTER {
r.mux.Lock()
if atomic.CompareAndSwapInt32((*int32)(&r.rpcClientStatus), (int32)(RUNNING), (int32)(UNHEALTHY)) {
logger.Infof("Connection is unregistered, switch server, connectionId=%s, request=%s",
r.currentConnection.getConnectionId(), request.GetRequestType())
r.switchServerAsync(ServerInfo{}, false)
}
r.mux.Unlock()
}
currentErr = waitReconnect(timeoutMills, &retryTimes, request, errors.New(response.GetMessage()))
continue
}
if response != nil && !response.IsSuccess() {
logger.Warnf("%s request received fail response, error code: %d, result code: %d, message: [%s]", request.GetRequestType(), response.GetErrorCode(), response.GetResultCode(), response.GetMessage())
}
r.lastActiveTimestamp.Store(time.Now())
return response, nil
}
if atomic.CompareAndSwapInt32((*int32)(&r.rpcClientStatus), int32(RUNNING), int32(UNHEALTHY)) {
r.switchServerAsync(ServerInfo{}, true)
}
if currentErr != nil {
return nil, currentErr
}
return nil, errors.New("request fail, unknown error")
}
func waitReconnect(timeoutMills int64, retryTimes *int, request rpc_request.IRequest, err error) error {
logger.Errorf("Send request fail, request=%s, body=%s, retryTimes=%v, error=%+v", request.GetRequestType(), request.GetBody(request), *retryTimes, err)
time.Sleep(time.Duration(math.Min(100, float64(timeoutMills/3))) * time.Millisecond)
*retryTimes++
return err
}
func (r *RpcClient) Name() string {
return r.name
}

View File

@ -0,0 +1,7 @@
package rpc
import "testing"
func TestHealthCheck(t *testing.T) {
}

View File

@ -0,0 +1,127 @@
/*
* 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_request
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
type ConfigRequest struct {
*Request
Group string `json:"group"`
DataId string `json:"dataId"`
Tenant string `json:"tenant"`
Module string `json:"module"`
}
func NewConfigRequest(group, dataId, tenant string) *ConfigRequest {
request := Request{
Headers: make(map[string]string, 8),
}
return &ConfigRequest{
Request: &request,
Group: group,
DataId: dataId,
Tenant: tenant,
Module: "config",
}
}
func (r *ConfigRequest) GetDataId() string {
return r.DataId
}
func (r *ConfigRequest) GetGroup() string {
return r.Group
}
func (r *ConfigRequest) GetTenant() string {
return r.Tenant
}
// request of listening a batch of configs.
type ConfigBatchListenRequest struct {
*ConfigRequest
Listen bool `json:"listen"`
ConfigListenContexts []model.ConfigListenContext `json:"configListenContexts"`
}
func NewConfigBatchListenRequest(cacheLen int) *ConfigBatchListenRequest {
return &ConfigBatchListenRequest{
Listen: true,
ConfigListenContexts: make([]model.ConfigListenContext, 0, cacheLen),
ConfigRequest: NewConfigRequest("", "", ""),
}
}
func (r *ConfigBatchListenRequest) GetRequestType() string {
return constant.CONFIG_BATCH_LISTEN_REQUEST_NAME
}
type ConfigChangeNotifyRequest struct {
*ConfigRequest
}
func NewConfigChangeNotifyRequest(group, dataId, tenant string) *ConfigChangeNotifyRequest {
return &ConfigChangeNotifyRequest{ConfigRequest: NewConfigRequest(group, dataId, tenant)}
}
func (r *ConfigChangeNotifyRequest) GetRequestType() string {
return constant.CONFIG_CHANGE_NOTIFY_REQUEST_NAME
}
type ConfigQueryRequest struct {
*ConfigRequest
Tag string `json:"tag"`
}
func NewConfigQueryRequest(group, dataId, tenant string) *ConfigQueryRequest {
return &ConfigQueryRequest{ConfigRequest: NewConfigRequest(group, dataId, tenant)}
}
func (r *ConfigQueryRequest) GetRequestType() string {
return constant.CONFIG_QUERY_REQUEST_NAME
}
type ConfigPublishRequest struct {
*ConfigRequest
Content string `json:"content"`
CasMd5 string `json:"casMd5"`
AdditionMap map[string]string `json:"additionMap"`
}
func NewConfigPublishRequest(group, dataId, tenant, content, casMd5 string) *ConfigPublishRequest {
return &ConfigPublishRequest{ConfigRequest: NewConfigRequest(group, dataId, tenant),
Content: content, CasMd5: casMd5, AdditionMap: make(map[string]string)}
}
func (r *ConfigPublishRequest) GetRequestType() string {
return constant.CONFIG_PUBLISH_REQUEST_NAME
}
type ConfigRemoveRequest struct {
*ConfigRequest
}
func NewConfigRemoveRequest(group, dataId, tenant string) *ConfigRemoveRequest {
return &ConfigRemoveRequest{ConfigRequest: NewConfigRequest(group, dataId, tenant)}
}
func (r *ConfigRemoveRequest) GetRequestType() string {
return constant.CONFIG_REMOVE_REQUEST_NAME
}

View File

@ -0,0 +1,99 @@
/*
* 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_request
type ClientAbilities struct {
}
type InternalRequest struct {
*Request
Module string `json:"module"`
}
func NewInternalRequest() *InternalRequest {
request := Request{
Headers: make(map[string]string, 8),
}
return &InternalRequest{
Request: &request,
Module: "internal",
}
}
type HealthCheckRequest struct {
*InternalRequest
}
func NewHealthCheckRequest() *HealthCheckRequest {
return &HealthCheckRequest{
InternalRequest: NewInternalRequest(),
}
}
func (r *HealthCheckRequest) GetRequestType() string {
return "HealthCheckRequest"
}
type ConnectResetRequest struct {
*InternalRequest
ServerIp string
ServerPort string
}
func (r *ConnectResetRequest) GetRequestType() string {
return "ConnectResetRequest"
}
type ClientDetectionRequest struct {
*InternalRequest
}
func (r *ClientDetectionRequest) GetRequestType() string {
return "ClientDetectionRequest"
}
type ServerCheckRequest struct {
*InternalRequest
}
func NewServerCheckRequest() *ServerCheckRequest {
return &ServerCheckRequest{
InternalRequest: NewInternalRequest(),
}
}
func (r *ServerCheckRequest) GetRequestType() string {
return "ServerCheckRequest"
}
type ConnectionSetupRequest struct {
*InternalRequest
ClientVersion string `json:"clientVersion"`
Tenant string `json:"tenant"`
Labels map[string]string `json:"labels"`
ClientAbilities ClientAbilities `json:"clientAbilities"`
}
func NewConnectionSetupRequest() *ConnectionSetupRequest {
return &ConnectionSetupRequest{
InternalRequest: NewInternalRequest(),
}
}
func (r *ConnectionSetupRequest) GetRequestType() string {
return "ConnectionSetupRequest"
}

View File

@ -0,0 +1,158 @@
/*
* 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_request
import (
"fmt"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
type NamingRequest struct {
*Request
Namespace string `json:"namespace"`
ServiceName string `json:"serviceName"`
GroupName string `json:"groupName"`
Module string `json:"module"`
}
func NewNamingRequest(namespace, serviceName, groupName string) *NamingRequest {
request := Request{
Headers: make(map[string]string, 8),
}
return &NamingRequest{
Request: &request,
Namespace: namespace,
ServiceName: serviceName,
GroupName: groupName,
Module: "naming",
}
}
func (r *NamingRequest) GetStringToSign() string {
data := strconv.FormatInt(time.Now().Unix()*1000, 10)
if r.ServiceName != "" || r.GroupName != "" {
data = fmt.Sprintf("%s@@%s@@%s", data, r.GroupName, r.ServiceName)
}
return data
}
type InstanceRequest struct {
*NamingRequest
Type string `json:"type"`
Instance model.Instance `json:"instance"`
}
func NewInstanceRequest(namespace, serviceName, groupName, Type string, instance model.Instance) *InstanceRequest {
return &InstanceRequest{
NamingRequest: NewNamingRequest(namespace, serviceName, groupName),
Type: Type,
Instance: instance,
}
}
func (r *InstanceRequest) GetRequestType() string {
return constant.INSTANCE_REQUEST_NAME
}
type BatchInstanceRequest struct {
*NamingRequest
Type string `json:"type"`
Instances []model.Instance `json:"instances"`
}
func NewBatchInstanceRequest(namespace, serviceName, groupName, Type string, instances []model.Instance) *BatchInstanceRequest {
return &BatchInstanceRequest{
NamingRequest: NewNamingRequest(namespace, serviceName, groupName),
Type: Type,
Instances: instances,
}
}
func (r *BatchInstanceRequest) GetRequestType() string {
return constant.BATCH_INSTANCE_REQUEST_NAME
}
type NotifySubscriberRequest struct {
*NamingRequest
ServiceInfo model.Service `json:"serviceInfo"`
}
func (r *NotifySubscriberRequest) GetRequestType() string {
return constant.NOTIFY_SUBSCRIBE_REQUEST_NAME
}
type ServiceListRequest struct {
*NamingRequest
PageNo int `json:"pageNo"`
PageSize int `json:"pageSize"`
Selector string `json:"selector"`
}
func NewServiceListRequest(namespace, serviceName, groupName string, pageNo, pageSize int, selector string) *ServiceListRequest {
return &ServiceListRequest{
NamingRequest: NewNamingRequest(namespace, serviceName, groupName),
PageNo: pageNo,
PageSize: pageSize,
Selector: selector,
}
}
func (r *ServiceListRequest) GetRequestType() string {
return constant.SERVICE_LIST_REQUEST_NAME
}
type SubscribeServiceRequest struct {
*NamingRequest
Subscribe bool `json:"subscribe"`
Clusters string `json:"clusters"`
}
func NewSubscribeServiceRequest(namespace, serviceName, groupName, clusters string, subscribe bool) *SubscribeServiceRequest {
return &SubscribeServiceRequest{
NamingRequest: NewNamingRequest(namespace, serviceName, groupName),
Subscribe: subscribe,
Clusters: clusters,
}
}
func (r *SubscribeServiceRequest) GetRequestType() string {
return constant.SUBSCRIBE_SERVICE_REQUEST_NAME
}
type ServiceQueryRequest struct {
*NamingRequest
Cluster string `json:"cluster"`
HealthyOnly bool `json:"healthyOnly"`
UdpPort int `json:"udpPort"`
}
func NewServiceQueryRequest(namespace, serviceName, groupName, cluster string, healthyOnly bool, udpPort int) *ServiceQueryRequest {
return &ServiceQueryRequest{
NamingRequest: NewNamingRequest(namespace, serviceName, groupName),
Cluster: cluster,
HealthyOnly: healthyOnly,
UdpPort: udpPort,
}
}
func (r *ServiceQueryRequest) GetRequestType() string {
return constant.SERVICE_QUERY_REQUEST_NAME
}

View File

@ -0,0 +1,67 @@
/*
* 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_request
import "github.com/nacos-group/nacos-sdk-go/v2/util"
type Request struct {
Headers map[string]string `json:"-"`
RequestId string `json:"requestId"`
}
type IRequest interface {
GetHeaders() map[string]string
GetRequestType() string
GetBody(request IRequest) string
PutAllHeaders(headers map[string]string)
GetRequestId() string
GetStringToSign() string
}
type IConfigRequest interface {
GetDataId() string
GetGroup() string
GetTenant() string
}
func (r *Request) PutAllHeaders(headers map[string]string) {
if r.Headers == nil {
r.Headers = make(map[string]string)
}
for k, v := range headers {
r.Headers[k] = v
}
}
func (r *Request) ClearHeaders() {
r.Headers = make(map[string]string)
}
func (r *Request) GetHeaders() map[string]string {
return r.Headers
}
func (r *Request) GetBody(request IRequest) string {
return util.ToJsonString(request)
}
func (r *Request) GetRequestId() string {
return r.RequestId
}
func (r *Request) GetStringToSign() string {
return ""
}

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_response
import "github.com/nacos-group/nacos-sdk-go/v2/model"
type ConfigChangeBatchListenResponse struct {
*Response
ChangedConfigs []model.ConfigContext `json:"changedConfigs"`
}
func (c *ConfigChangeBatchListenResponse) GetResponseType() string {
return "ConfigChangeBatchListenResponse"
}
type ConfigQueryResponse struct {
*Response
Content string `json:"content"`
EncryptedDataKey string `json:"encryptedDataKey"`
ContentType string `json:"contentType"`
Md5 string `json:"md5"`
LastModified int64 `json:"lastModified"`
IsBeta bool `json:"isBeta"`
Tag bool `json:"tag"`
}
func (c *ConfigQueryResponse) GetResponseType() string {
return "ConfigQueryResponse"
}
type ConfigPublishResponse struct {
*Response
}
func (c *ConfigPublishResponse) GetResponseType() string {
return "ConfigPublishResponse"
}
type ConfigRemoveResponse struct {
*Response
}
func (c *ConfigRemoveResponse) GetResponseType() string {
return "ConfigRemoveResponse"
}

View File

@ -0,0 +1,10 @@
package rpc_response
type ResponseCode int
const (
ResponseSuccessCode ResponseCode = 200
ResponseFailCode ResponseCode = 500
ResponseSuccessField = "success"
)

View File

@ -0,0 +1,122 @@
/*
* 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_response
import (
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
type ConnectResetResponse struct {
*Response
}
func (c *ConnectResetResponse) GetResponseType() string {
return "ConnectResetResponse"
}
type ClientDetectionResponse struct {
*Response
}
func (c *ClientDetectionResponse) GetResponseType() string {
return "ClientDetectionResponse"
}
type ServerCheckResponse struct {
*Response
ConnectionId string `json:"connectionId"`
}
func (c *ServerCheckResponse) GetResponseType() string {
return "ServerCheckResponse"
}
type InstanceResponse struct {
*Response
}
func (c *InstanceResponse) GetResponseType() string {
return "InstanceResponse"
}
type BatchInstanceResponse struct {
*Response
}
func (c *BatchInstanceResponse) GetResponseType() string {
return "BatchInstanceResponse"
}
type QueryServiceResponse struct {
*Response
ServiceInfo model.Service `json:"serviceInfo"`
}
func (c *QueryServiceResponse) GetResponseType() string {
return "QueryServiceResponse"
}
type SubscribeServiceResponse struct {
*Response
ServiceInfo model.Service `json:"serviceInfo"`
}
func (c *SubscribeServiceResponse) GetResponseType() string {
return "SubscribeServiceResponse"
}
type ServiceListResponse struct {
*Response
Count int `json:"count"`
ServiceNames []string `json:"serviceNames"`
}
func (c *ServiceListResponse) GetResponseType() string {
return "ServiceListResponse"
}
type NotifySubscriberResponse struct {
*Response
}
func (c *NotifySubscriberResponse) GetResponseType() string {
return "NotifySubscriberResponse"
}
type HealthCheckResponse struct {
*Response
}
func (c *HealthCheckResponse) GetResponseType() string {
return "HealthCheckResponse"
}
type ErrorResponse struct {
*Response
}
func (c *ErrorResponse) GetResponseType() string {
return "ErrorResponse"
}
type MockResponse struct {
*Response
}
func (c *MockResponse) GetResponseType() string {
return "MockResponse"
}

View File

@ -0,0 +1,158 @@
/*
* 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_response
import (
"strconv"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
var ClientResponseMapping map[string]func() IResponse
func init() {
ClientResponseMapping = make(map[string]func() IResponse)
registerClientResponses()
}
type IResponse interface {
GetResponseType() string
SetRequestId(requestId string)
GetBody() string
GetErrorCode() int
IsSuccess() bool
SetSuccess(bool)
GetResultCode() int
GetMessage() string
}
type Response struct {
ResultCode int `json:"resultCode"`
ErrorCode int `json:"errorCode"`
Success bool `json:"success"`
Message string `json:"message"`
RequestId string `json:"requestId"`
}
func (r *Response) SetRequestId(requestId string) {
r.RequestId = requestId
}
func (r *Response) GetBody() string {
return util.ToJsonString(r)
}
func (r *Response) IsSuccess() bool {
return r.Success
}
func (r *Response) SetSuccess(successResult bool) {
r.Success = successResult
}
func (r *Response) GetErrorCode() int {
return r.ErrorCode
}
func (r *Response) GetResultCode() int {
return r.ResultCode
}
func (r *Response) GetMessage() string {
return r.Message
}
func registerClientResponse(response func() IResponse) {
responseType := response().GetResponseType()
if responseType == "" {
logger.Errorf("Register client response error: responseType is nil")
return
}
ClientResponseMapping[responseType] = response
}
func registerClientResponses() {
// register InstanceResponse.
registerClientResponse(func() IResponse {
return &InstanceResponse{Response: &Response{}}
})
// register BatchInstanceResponse.
registerClientResponse(func() IResponse {
return &BatchInstanceResponse{Response: &Response{}}
})
// register QueryServiceResponse.
registerClientResponse(func() IResponse {
return &QueryServiceResponse{Response: &Response{}}
})
// register SubscribeServiceResponse.
registerClientResponse(func() IResponse {
return &SubscribeServiceResponse{Response: &Response{}}
})
// register ServiceListResponse.
registerClientResponse(func() IResponse {
return &ServiceListResponse{Response: &Response{}}
})
// register NotifySubscriberResponse.
registerClientResponse(func() IResponse {
return &NotifySubscriberResponse{Response: &Response{}}
})
// register HealthCheckResponse.
registerClientResponse(func() IResponse {
return &HealthCheckResponse{Response: &Response{}}
})
// register ErrorResponse.
registerClientResponse(func() IResponse {
return &ErrorResponse{Response: &Response{}}
})
//register ConfigChangeBatchListenResponse
registerClientResponse(func() IResponse {
return &ConfigChangeBatchListenResponse{Response: &Response{}}
})
//register ConfigQueryResponse
registerClientResponse(func() IResponse {
return &ConfigQueryResponse{Response: &Response{}}
})
//register ConfigPublishResponse
registerClientResponse(func() IResponse {
return &ConfigPublishResponse{Response: &Response{}}
})
//register ConfigRemoveResponse
registerClientResponse(func() IResponse {
return &ConfigRemoveResponse{Response: &Response{}}
})
}
// get grpc response status code with NA default.
func GetGrpcResponseStatusCode(response IResponse) string {
if response != nil {
return strconv.Itoa(response.GetResultCode())
} else {
return "NA"
}
}

View File

@ -0,0 +1,40 @@
package rpc_response
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestRpcResponseIsSuccess(t *testing.T) {
responseBody0 := `{"resultCode":200,"errorCode":0}`
responseBody1 := `{"resultCode":200,"errorCode":0,"success":true}`
responseBody2 := `{"resultCode":200,"errorCode":0,"success":"true"}`
responseBody3 := `{"resultCode":200,"errorCode":0,"success":false}`
responseBody4 := `{"resultCode":500,"errorCode":0,"success":true}`
responseBody5 := `{"resultCode":500,"errorCode":0,"success":false}`
responseBodyList := make([]string, 0)
responseBodyList = append(responseBodyList, responseBody0, responseBody1, responseBody2, responseBody3, responseBody4, responseBody5)
for k, v := range ClientResponseMapping {
t.Run("test "+k, func(t *testing.T) {
for index, responseBody := range responseBodyList {
response, err := InnerResponseJsonUnmarshal([]byte(responseBody), v)
switch index {
case 0, 1, 4:
assert.True(t, response.IsSuccess())
break
case 3, 5:
assert.False(t, response.IsSuccess())
break
case 2:
assert.Nil(t, response)
assert.NotNil(t, err)
t.Logf("handle %d failed with responseBody: %s", index, responseBody)
break
default:
panic("unknown index")
}
}
})
}
}

View File

@ -0,0 +1,24 @@
package rpc_response
import "encoding/json"
func InnerResponseJsonUnmarshal(responseBody []byte, responseFunc func() IResponse) (IResponse, error) {
response := responseFunc()
err := json.Unmarshal(responseBody, response)
if err != nil {
return nil, err
}
if !response.IsSuccess() {
tempFiledMap := make(map[string]interface{})
err = json.Unmarshal(responseBody, &tempFiledMap)
if err != nil {
return response, nil
}
if _, ok := tempFiledMap[ResponseSuccessField]; !ok {
response.SetSuccess(response.GetResultCode() == int(ResponseSuccessCode))
}
}
return response, err
}

View File

@ -0,0 +1,101 @@
/*
* 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 (
"strconv"
"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/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
)
// IServerRequestHandler to process the request from server side.
type IServerRequestHandler interface {
Name() string
//RequestReply Handle request from server.
RequestReply(request rpc_request.IRequest, rpcClient *RpcClient) rpc_response.IResponse
}
type ConnectResetRequestHandler struct {
}
func (c *ConnectResetRequestHandler) Name() string {
return "ConnectResetRequestHandler"
}
func (c *ConnectResetRequestHandler) RequestReply(request rpc_request.IRequest, rpcClient *RpcClient) rpc_response.IResponse {
connectResetRequest, ok := request.(*rpc_request.ConnectResetRequest)
if ok {
rpcClient.mux.Lock()
defer rpcClient.mux.Unlock()
if rpcClient.IsRunning() {
if connectResetRequest.ServerIp != "" {
serverPortNum, err := strconv.Atoi(connectResetRequest.ServerPort)
if err != nil {
logger.Errorf("ConnectResetRequest ServerPort type conversion error:%+v", err)
return nil
}
rpcClient.switchServerAsync(ServerInfo{serverIp: connectResetRequest.ServerIp, serverPort: uint64(serverPortNum)}, false)
} else {
rpcClient.switchServerAsync(ServerInfo{}, true)
}
}
return &rpc_response.ConnectResetResponse{Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS}}
}
return nil
}
type ClientDetectionRequestHandler struct {
}
func (c *ClientDetectionRequestHandler) Name() string {
return "ClientDetectionRequestHandler"
}
func (c *ClientDetectionRequestHandler) RequestReply(request rpc_request.IRequest, _ *RpcClient) rpc_response.IResponse {
_, ok := request.(*rpc_request.ClientDetectionRequest)
if ok {
return &rpc_response.ClientDetectionResponse{
Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS},
}
}
return nil
}
type NamingPushRequestHandler struct {
ServiceInfoHolder *naming_cache.ServiceInfoHolder
}
func (*NamingPushRequestHandler) Name() string {
return "NamingPushRequestHandler"
}
func (c *NamingPushRequestHandler) RequestReply(request rpc_request.IRequest, client *RpcClient) rpc_response.IResponse {
notifySubscriberRequest, ok := request.(*rpc_request.NotifySubscriberRequest)
if ok {
c.ServiceInfoHolder.ProcessService(&notifySubscriberRequest.ServiceInfo)
logger.Debugf("%s naming push response success ackId->%s", client.currentConnection.getConnectionId(),
request.GetRequestId())
return &rpc_response.NotifySubscriberResponse{
Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS, Success: true},
}
}
return nil
}

View File

@ -0,0 +1,185 @@
package security
import (
"context"
"encoding/json"
"io"
"net/http"
"strconv"
"strings"
"sync/atomic"
"time"
"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/pkg/errors"
)
type NacosAuthClient struct {
username string
password string
accessToken *atomic.Value
tokenTtl int64
lastRefreshTime int64
tokenRefreshWindow int64
agent http_agent.IHttpAgent
clientCfg constant.ClientConfig
serverCfgs []constant.ServerConfig
}
func NewNacosAuthClient(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig, agent http_agent.IHttpAgent) *NacosAuthClient {
client := &NacosAuthClient{
username: clientCfg.Username,
password: clientCfg.Password,
serverCfgs: serverCfgs,
clientCfg: clientCfg,
agent: agent,
accessToken: &atomic.Value{},
}
return client
}
func (ac *NacosAuthClient) GetAccessToken() string {
v := ac.accessToken.Load()
if v == nil {
return ""
}
return v.(string)
}
func (ac *NacosAuthClient) GetSecurityInfo(resource RequestResource) map[string]string {
var securityInfo = make(map[string]string, 4)
v := ac.accessToken.Load()
if v != nil {
securityInfo[constant.KEY_ACCESS_TOKEN] = v.(string)
}
return securityInfo
}
func (ac *NacosAuthClient) AutoRefresh(ctx context.Context) {
// If the username is not set, the automatic refresh Token is not enabled
if ac.username == "" {
return
}
go func() {
var timer *time.Timer
if lastLoginSuccess := ac.lastRefreshTime > 0 && ac.tokenTtl > 0 && ac.tokenRefreshWindow > 0; lastLoginSuccess {
timer = time.NewTimer(time.Second * time.Duration(ac.tokenTtl-ac.tokenRefreshWindow))
} else {
timer = time.NewTimer(time.Second * time.Duration(5))
}
defer timer.Stop()
for {
select {
case <-timer.C:
_, err := ac.Login()
if err != nil {
logger.Errorf("login has error %+v", err)
timer.Reset(time.Second * time.Duration(5))
} else {
logger.Infof("login success, tokenTtl: %+v seconds, tokenRefreshWindow: %+v seconds", ac.tokenTtl, ac.tokenRefreshWindow)
timer.Reset(time.Second * time.Duration(ac.tokenTtl-ac.tokenRefreshWindow))
}
case <-ctx.Done():
return
}
}
}()
}
func (ac *NacosAuthClient) Login() (bool, error) {
var throwable error = nil
for i := 0; i < len(ac.serverCfgs); i++ {
result, err := ac.login(ac.serverCfgs[i])
throwable = err
if result {
return true, nil
}
}
return false, throwable
}
func (ac *NacosAuthClient) UpdateServerList(serverList []constant.ServerConfig) {
ac.serverCfgs = serverList
}
func (ac *NacosAuthClient) GetServerList() []constant.ServerConfig {
return ac.serverCfgs
}
func (ac *NacosAuthClient) login(server constant.ServerConfig) (bool, error) {
if ac.lastRefreshTime > 0 && ac.tokenTtl > 0 {
// We refresh 2 windows before expiration to ensure continuous availability
tokenRefreshTime := ac.lastRefreshTime + ac.tokenTtl - 2*ac.tokenRefreshWindow
if time.Now().Unix() < tokenRefreshTime {
return true, nil
}
}
if ac.username == "" {
ac.lastRefreshTime = time.Now().Unix()
return true, nil
}
contextPath := server.ContextPath
if !strings.HasPrefix(contextPath, "/") {
contextPath = "/" + contextPath
}
if strings.HasSuffix(contextPath, "/") {
contextPath = contextPath[0 : len(contextPath)-1]
}
if server.Scheme == "" {
server.Scheme = "http"
}
reqUrl := server.Scheme + "://" + server.IpAddr + ":" + strconv.FormatInt(int64(server.Port), 10) + contextPath + "/v1/auth/users/login"
header := http.Header{
"content-type": []string{"application/x-www-form-urlencoded"},
}
resp, err := ac.agent.Post(reqUrl, header, ac.clientCfg.TimeoutMs, map[string]string{
"username": ac.username,
"password": ac.password,
})
if err != nil {
return false, err
}
var bytes []byte
bytes, err = io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return false, err
}
if resp.StatusCode != constant.RESPONSE_CODE_SUCCESS {
errMsg := string(bytes)
return false, errors.New(errMsg)
}
var result map[string]interface{}
err = json.Unmarshal(bytes, &result)
if err != nil {
return false, err
}
if val, ok := result[constant.KEY_ACCESS_TOKEN]; ok {
ac.accessToken.Store(val)
ac.lastRefreshTime = time.Now().Unix()
ac.tokenTtl = int64(result[constant.KEY_TOKEN_TTL].(float64))
ac.tokenRefreshWindow = ac.tokenTtl / 10
}
return true, nil
}

View File

@ -0,0 +1,289 @@
package security
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/stretchr/testify/assert"
)
// MockResponseBody creates a mock response body for testing
type MockResponseBody struct {
*bytes.Buffer
}
func (m *MockResponseBody) Close() error {
return nil
}
func NewMockResponseBody(data interface{}) io.ReadCloser {
var buf bytes.Buffer
if str, ok := data.(string); ok {
buf.WriteString(str)
} else {
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.Encode(data)
}
return &MockResponseBody{&buf}
}
// MockHttpAgent implements http_agent.IHttpAgent for testing
type MockHttpAgent struct {
PostFunc func(url string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error)
}
func (m *MockHttpAgent) Request(method string, url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
switch method {
case http.MethodPost:
return m.Post(url, header, timeoutMs, params)
default:
return &http.Response{
StatusCode: http.StatusMethodNotAllowed,
Body: NewMockResponseBody("method not allowed"),
}, nil
}
}
func (m *MockHttpAgent) RequestOnlyResult(method string, url string, header http.Header, timeoutMs uint64, params map[string]string) string {
resp, err := m.Request(method, url, header, timeoutMs, params)
if err != nil {
return ""
}
if resp.Body == nil {
return ""
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return ""
}
return string(data)
}
func (m *MockHttpAgent) Get(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return m.Request(http.MethodGet, url, header, timeoutMs, params)
}
func (m *MockHttpAgent) Post(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
if m.PostFunc != nil {
return m.PostFunc(url, header, timeoutMs, params)
}
return &http.Response{
StatusCode: http.StatusNotImplemented,
Body: NewMockResponseBody("not implemented"),
}, nil
}
func (m *MockHttpAgent) Delete(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return m.Request(http.MethodDelete, url, header, timeoutMs, params)
}
func (m *MockHttpAgent) Put(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return m.Request(http.MethodPut, url, header, timeoutMs, params)
}
func TestNacosAuthClient_Login_Success(t *testing.T) {
// Setup mock response
mockResp := &http.Response{
StatusCode: constant.RESPONSE_CODE_SUCCESS,
Body: NewMockResponseBody(map[string]interface{}{
constant.KEY_ACCESS_TOKEN: "test-token",
constant.KEY_TOKEN_TTL: float64(10),
}),
}
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
// Verify request parameters
assert.Equal(t, "test-user", params["username"])
assert.Equal(t, "test-pass", params["password"])
contentType := header["content-type"]
assert.Equal(t, []string{"application/x-www-form-urlencoded"}, contentType)
return mockResp, nil
},
}
// Create client config
clientConfig := constant.ClientConfig{
Username: "test-user",
Password: "test-pass",
TimeoutMs: 10000,
}
serverConfigs := []constant.ServerConfig{
{
IpAddr: "127.0.0.1",
Port: 8848,
ContextPath: "/nacos",
},
}
client := NewNacosAuthClient(clientConfig, serverConfigs, mockAgent)
// Test login
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
// Verify token is stored
assert.Equal(t, "test-token", client.GetAccessToken())
}
func TestNacosAuthClient_Login_NoAuth(t *testing.T) {
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
t.Fatal("Should not make HTTP call when no username is set")
return nil, nil
},
}
clientConfig := constant.ClientConfig{}
serverConfigs := []constant.ServerConfig{{}}
client := NewNacosAuthClient(clientConfig, serverConfigs, mockAgent)
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
assert.Empty(t, client.GetAccessToken())
}
func TestNacosAuthClient_TokenRefresh(t *testing.T) {
callCount := 0
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
callCount++
return &http.Response{
StatusCode: constant.RESPONSE_CODE_SUCCESS,
Body: NewMockResponseBody(map[string]interface{}{
constant.KEY_ACCESS_TOKEN: "token-" + fmt.Sprintf("%d", callCount),
constant.KEY_TOKEN_TTL: float64(1), // 1 second TTL for quick testing
}),
}, nil
},
}
clientConfig := constant.ClientConfig{
Username: "test-user",
Password: "test-pass",
}
client := NewNacosAuthClient(clientConfig, []constant.ServerConfig{{IpAddr: "localhost"}}, mockAgent)
// Initial login
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
assert.Equal(t, "token-1", client.GetAccessToken())
// Wait for token to require refresh (1 second TTL)
time.Sleep(time.Second * 2)
// Second login should get new token
success, err = client.Login()
assert.NoError(t, err)
assert.True(t, success)
assert.Equal(t, "token-2", client.GetAccessToken())
}
func TestNacosAuthClient_AutoRefresh(t *testing.T) {
callCount := 0
tokenChan := make(chan string, 2)
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
callCount++
token := fmt.Sprintf("auto-token-%d", callCount)
tokenChan <- token
t.Logf("Mock server received request #%d, returning token: %s", callCount, token)
return &http.Response{
StatusCode: constant.RESPONSE_CODE_SUCCESS,
Body: NewMockResponseBody(map[string]interface{}{
constant.KEY_ACCESS_TOKEN: token,
constant.KEY_TOKEN_TTL: float64(10), // 10 seconds TTL, resulting in 1s refresh window
}),
}, nil
},
}
clientConfig := constant.ClientConfig{
Username: "test-user",
Password: "test-pass",
}
client := NewNacosAuthClient(clientConfig, []constant.ServerConfig{{IpAddr: "localhost"}}, mockAgent)
// First do a manual login
t.Log("Performing initial manual login")
success, err := client.Login()
assert.NoError(t, err)
assert.True(t, success)
token1 := <-tokenChan // Get the token from the first login
t.Logf("Initial login successful, token: %s", token1)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
// Start auto refresh
t.Log("Starting auto refresh")
client.AutoRefresh(ctx)
// Wait for token refresh (should happen after TTL-2*refreshWindow seconds = 8 seconds)
// We'll wait a bit longer to account for any delays
t.Log("Waiting for token refresh")
var token2 string
select {
case token2 = <-tokenChan:
t.Logf("Received refreshed token: %s", token2)
case <-time.After(time.Second * 12):
t.Fatal("Timeout waiting for token refresh")
}
assert.NotEqual(t, token1, token2, "Token should have been refreshed")
assert.Equal(t, "auto-token-1", token1, "First token should be auto-token-1")
assert.Equal(t, "auto-token-2", token2, "Second token should be auto-token-2")
}
func TestNacosAuthClient_GetSecurityInfo(t *testing.T) {
client := NewNacosAuthClient(constant.ClientConfig{}, []constant.ServerConfig{}, nil)
// When no token
info := client.GetSecurityInfo(RequestResource{})
assert.Empty(t, info[constant.KEY_ACCESS_TOKEN])
// When token exists
mockToken := "test-security-token"
client.accessToken.Store(mockToken)
info = client.GetSecurityInfo(RequestResource{})
assert.Equal(t, mockToken, info[constant.KEY_ACCESS_TOKEN])
}
func TestNacosAuthClient_LoginFailure(t *testing.T) {
mockAgent := &MockHttpAgent{
PostFunc: func(url string, header http.Header, timeoutMs uint64, params map[string]string) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: NewMockResponseBody("Invalid credentials"),
}, nil
},
}
client := NewNacosAuthClient(
constant.ClientConfig{Username: "wrong-user", Password: "wrong-pass"},
[]constant.ServerConfig{{IpAddr: "localhost"}},
mockAgent,
)
success, err := client.Login()
assert.Error(t, err)
assert.False(t, success)
assert.Empty(t, client.GetAccessToken())
}

View File

@ -0,0 +1,95 @@
package security
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
type RamContext struct {
SignatureRegionId string
AccessKey string
SecretKey string
SecurityToken string
EphemeralAccessKeyId bool
}
type RamAuthClient struct {
clientConfig constant.ClientConfig
ramCredentialProviders []RamCredentialProvider
resourceInjector map[string]ResourceInjector
matchedProvider RamCredentialProvider
}
func NewRamAuthClient(clientCfg constant.ClientConfig) *RamAuthClient {
var providers = []RamCredentialProvider{
&RamRoleArnCredentialProvider{
clientConfig: clientCfg,
},
&EcsRamRoleCredentialProvider{
clientConfig: clientCfg,
},
&OIDCRoleArnCredentialProvider{
clientConfig: clientCfg,
},
&CredentialsURICredentialProvider{
clientConfig: clientCfg,
},
&AutoRotateCredentialProvider{
clientConfig: clientCfg,
},
&StsTokenCredentialProvider{
clientConfig: clientCfg,
},
&AccessKeyCredentialProvider{
clientConfig: clientCfg,
},
}
injectors := map[string]ResourceInjector{
REQUEST_TYPE_NAMING: &NamingResourceInjector{},
REQUEST_TYPE_CONFIG: &ConfigResourceInjector{},
}
return &RamAuthClient{
clientConfig: clientCfg,
ramCredentialProviders: providers,
resourceInjector: injectors,
}
}
func NewRamAuthClientWithProvider(clientCfg constant.ClientConfig, ramCredentialProvider RamCredentialProvider) *RamAuthClient {
ramAuthClient := NewRamAuthClient(clientCfg)
if ramCredentialProvider != nil {
ramAuthClient.ramCredentialProviders = append(ramAuthClient.ramCredentialProviders, ramCredentialProvider)
}
return ramAuthClient
}
func (rac *RamAuthClient) Login() (bool, error) {
for _, provider := range rac.ramCredentialProviders {
if provider.MatchProvider() {
rac.matchedProvider = provider
break
}
}
if rac.matchedProvider == nil {
return false, nil
}
err := rac.matchedProvider.Init()
if err != nil {
return false, err
}
return true, nil
}
func (rac *RamAuthClient) GetSecurityInfo(resource RequestResource) map[string]string {
var securityInfo = make(map[string]string, 4)
if rac.matchedProvider == nil {
return securityInfo
}
ramContext := rac.matchedProvider.GetCredentialsForNacosClient()
rac.resourceInjector[resource.requestType].doInject(resource, ramContext, securityInfo)
return securityInfo
}
func (rac *RamAuthClient) UpdateServerList(serverList []constant.ServerConfig) {
return
}

View File

@ -0,0 +1,380 @@
package security
import (
"encoding/json"
"os"
"strconv"
"github.com/aliyun/aliyun-secretsmanager-client-go/sdk"
"github.com/aliyun/credentials-go/credentials"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
const (
ENV_PREFIX string = "ALIBABA_CLOUD_"
ACCESS_KEY_ID_KEY string = ENV_PREFIX + "ACCESS_KEY_ID"
ACCESS_KEY_SECRET_KEY string = ENV_PREFIX + "ACCESS_KEY_SECRET"
SECURITY_TOKEN_KEY string = ENV_PREFIX + "SECURITY_TOKEN"
SIGNATURE_REGION_ID_KEY string = ENV_PREFIX + "SIGNATURE_REGION_ID"
RAM_ROLE_NAME_KEY string = ENV_PREFIX + "RAM_ROLE_NAME"
ROLE_ARN_KEY string = ENV_PREFIX + "ROLE_ARN"
ROLE_SESSION_NAME_KEY string = ENV_PREFIX + "ROLE_SESSION_NAME"
ROLE_SESSION_EXPIRATION_KEY string = ENV_PREFIX + "ROLE_SESSION_EXPIRATION"
POLICY_KEY string = ENV_PREFIX + "POLICY"
OIDC_PROVIDER_ARN_KEY string = ENV_PREFIX + "OIDC_PROVIDER_ARN"
OIDC_TOKEN_FILE_KEY string = ENV_PREFIX + "OIDC_TOKEN_FILE"
CREDENTIALS_URI_KEY string = ENV_PREFIX + "CREDENTIALS_URI"
SECRET_NAME_KEY string = ENV_PREFIX + "SECRET_NAME"
)
func GetNacosProperties(property string, envKey string) string {
if property != "" {
return property
} else {
return os.Getenv(envKey)
}
}
type RamCredentialProvider interface {
MatchProvider() bool
Init() error
GetCredentialsForNacosClient() RamContext
}
type AccessKeyCredentialProvider struct {
clientConfig constant.ClientConfig
accessKey string
secretKey string
signatureRegionId string
}
func (provider *AccessKeyCredentialProvider) MatchProvider() bool {
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
return accessKey != "" && secretKey != ""
}
func (provider *AccessKeyCredentialProvider) Init() error {
provider.accessKey = GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
provider.secretKey = GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
if provider.clientConfig.RamConfig != nil {
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
} else {
provider.signatureRegionId = ""
}
return nil
}
func (provider *AccessKeyCredentialProvider) GetCredentialsForNacosClient() RamContext {
ramContext := RamContext{
AccessKey: provider.accessKey,
SecretKey: provider.secretKey,
SignatureRegionId: provider.signatureRegionId,
}
return ramContext
}
type AutoRotateCredentialProvider struct {
clientConfig constant.ClientConfig
secretManagerCacheClient *sdk.SecretManagerCacheClient
secretName string
signatureRegionId string
}
func (provider *AutoRotateCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
secretName := GetNacosProperties(provider.clientConfig.RamConfig.SecretName, SECRET_NAME_KEY)
return secretName != ""
}
func (provider *AutoRotateCredentialProvider) Init() error {
secretName := GetNacosProperties(provider.clientConfig.RamConfig.SecretName, SECRET_NAME_KEY)
client, err := sdk.NewClient()
if err != nil {
return err
}
provider.secretManagerCacheClient = client
provider.secretName = secretName
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *AutoRotateCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.secretManagerCacheClient == nil || provider.secretName == "" {
return RamContext{}
}
secretInfo, err := provider.secretManagerCacheClient.GetSecretInfo(provider.secretName)
if err != nil {
return RamContext{}
}
var m map[string]string
err = json.Unmarshal([]byte(secretInfo.SecretValue), &m)
if err != nil {
return RamContext{}
}
accessKeyId := m["AccessKeyId"]
accessKeySecret := m["AccessKeySecret"]
ramContext := RamContext{
AccessKey: accessKeyId,
SecretKey: accessKeySecret,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: false,
}
return ramContext
}
type StsTokenCredentialProvider struct {
clientConfig constant.ClientConfig
accessKey string
secretKey string
securityToken string
signatureRegionId string
}
func (provider *StsTokenCredentialProvider) MatchProvider() bool {
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
if provider.clientConfig.RamConfig == nil {
return false
}
stsToken := GetNacosProperties(provider.clientConfig.RamConfig.SecurityToken, SECURITY_TOKEN_KEY)
return accessKey != "" && secretKey != "" && stsToken != ""
}
func (provider *StsTokenCredentialProvider) Init() error {
provider.accessKey = GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
provider.secretKey = GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
provider.securityToken = GetNacosProperties(provider.clientConfig.RamConfig.SecurityToken, SECURITY_TOKEN_KEY)
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *StsTokenCredentialProvider) GetCredentialsForNacosClient() RamContext {
ramContext := RamContext{
AccessKey: provider.accessKey,
SecretKey: provider.secretKey,
SecurityToken: provider.securityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
return ramContext
}
type EcsRamRoleCredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *EcsRamRoleCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
ramRoleName := GetNacosProperties(provider.clientConfig.RamConfig.RamRoleName, RAM_ROLE_NAME_KEY)
return ramRoleName != ""
}
func (provider *EcsRamRoleCredentialProvider) Init() error {
ramRoleName := GetNacosProperties(provider.clientConfig.RamConfig.RamRoleName, RAM_ROLE_NAME_KEY)
credentialsConfig := new(credentials.Config).SetType("ecs_ram_role").SetRoleName(ramRoleName)
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *EcsRamRoleCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
ramContext := RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
return ramContext
}
type RamRoleArnCredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *RamRoleArnCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
roleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
oidcProviderArn := GetNacosProperties(provider.clientConfig.RamConfig.OIDCProviderArn, OIDC_PROVIDER_ARN_KEY)
return accessKey == "" && secretKey == "" && roleArn != "" && roleSessionName != "" && oidcProviderArn == ""
}
func (provider *RamRoleArnCredentialProvider) Init() error {
accessKey := GetNacosProperties(provider.clientConfig.AccessKey, ACCESS_KEY_ID_KEY)
secretKey := GetNacosProperties(provider.clientConfig.SecretKey, ACCESS_KEY_SECRET_KEY)
roleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
credentialsConfig := new(credentials.Config).SetType("ram_role_arn").
SetAccessKeyId(accessKey).SetAccessKeySecret(secretKey).
SetRoleArn(roleArn).SetRoleSessionName(roleSessionName)
if roleSessionExpiration := GetNacosProperties(strconv.Itoa(provider.clientConfig.RamConfig.RoleSessionExpiration), ROLE_SESSION_EXPIRATION_KEY); roleSessionExpiration != "" {
if roleSessionExpirationTime, err := strconv.Atoi(roleSessionExpiration); err == nil {
if roleSessionExpirationTime == 0 {
roleSessionExpirationTime = 3600
}
credentialsConfig.SetRoleSessionExpiration(roleSessionExpirationTime)
}
}
policy := GetNacosProperties(provider.clientConfig.RamConfig.Policy, POLICY_KEY)
if policy != "" {
credentialsConfig.SetPolicy(policy)
}
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *RamRoleArnCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
return RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
}
type OIDCRoleArnCredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *OIDCRoleArnCredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
roleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
oidcProviderArn := GetNacosProperties(provider.clientConfig.RamConfig.OIDCProviderArn, OIDC_PROVIDER_ARN_KEY)
oidcTokenFile := GetNacosProperties(provider.clientConfig.RamConfig.OIDCTokenFilePath, OIDC_TOKEN_FILE_KEY)
return roleArn != "" && roleSessionName != "" && oidcProviderArn != "" && oidcTokenFile != ""
}
func (provider *OIDCRoleArnCredentialProvider) Init() error {
ramRoleArn := GetNacosProperties(provider.clientConfig.RamConfig.RoleArn, ROLE_ARN_KEY)
roleSessionName := GetNacosProperties(provider.clientConfig.RamConfig.RoleSessionName, ROLE_SESSION_NAME_KEY)
oidcProviderArn := GetNacosProperties(provider.clientConfig.RamConfig.OIDCProviderArn, OIDC_PROVIDER_ARN_KEY)
oidcTokenFilePath := GetNacosProperties(provider.clientConfig.RamConfig.OIDCTokenFilePath, OIDC_TOKEN_FILE_KEY)
credentialsConfig := new(credentials.Config).SetType("oidc_role_arn").
SetRoleArn(ramRoleArn).SetRoleSessionName(roleSessionName).
SetOIDCProviderArn(oidcProviderArn).SetOIDCTokenFilePath(oidcTokenFilePath)
if roleSessionExpiration := GetNacosProperties(strconv.Itoa(provider.clientConfig.RamConfig.RoleSessionExpiration), ROLE_SESSION_EXPIRATION_KEY); roleSessionExpiration != "" {
if roleSessionExpirationTime, err := strconv.Atoi(roleSessionExpiration); err == nil {
if roleSessionExpirationTime == 0 {
roleSessionExpirationTime = 3600
}
credentialsConfig.SetRoleSessionExpiration(roleSessionExpirationTime)
}
}
policy := GetNacosProperties(provider.clientConfig.RamConfig.Policy, POLICY_KEY)
if policy != "" {
credentialsConfig.SetPolicy(policy)
}
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *OIDCRoleArnCredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
return RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
}
type CredentialsURICredentialProvider struct {
clientConfig constant.ClientConfig
credentialClient credentials.Credential
signatureRegionId string
}
func (provider *CredentialsURICredentialProvider) MatchProvider() bool {
if provider.clientConfig.RamConfig == nil {
return false
}
credentialsURI := GetNacosProperties(provider.clientConfig.RamConfig.CredentialsURI, CREDENTIALS_URI_KEY)
return credentialsURI != ""
}
func (provider *CredentialsURICredentialProvider) Init() error {
credentialsURI := GetNacosProperties(provider.clientConfig.RamConfig.CredentialsURI, CREDENTIALS_URI_KEY)
credentialsConfig := new(credentials.Config).SetType("credentials_uri").SetURLCredential(credentialsURI)
credentialClient, err := credentials.NewCredential(credentialsConfig)
if err != nil {
return err
}
provider.credentialClient = credentialClient
provider.signatureRegionId = GetNacosProperties(provider.clientConfig.RamConfig.SignatureRegionId, SIGNATURE_REGION_ID_KEY)
return nil
}
func (provider *CredentialsURICredentialProvider) GetCredentialsForNacosClient() RamContext {
if provider.credentialClient == nil {
return RamContext{}
}
if provider.credentialClient == nil {
return RamContext{}
}
credential, err := provider.credentialClient.GetCredential()
if err != nil {
return RamContext{}
}
return RamContext{
AccessKey: *credential.AccessKeyId,
SecretKey: *credential.AccessKeySecret,
SecurityToken: *credential.SecurityToken,
SignatureRegionId: provider.signatureRegionId,
EphemeralAccessKeyId: true,
}
}

View File

@ -0,0 +1,127 @@
package security
import (
"fmt"
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
)
type ResourceInjector interface {
doInject(resource RequestResource, ramContext RamContext, param map[string]string)
}
const (
CONFIG_AK_FILED string = "Spas-AccessKey"
NAMING_AK_FILED string = "ak"
SECURITY_TOKEN_HEADER string = "Spas-SecurityToken"
SIGNATURE_VERSION_HEADER string = "signatureVersion"
SIGNATURE_VERSION_V4 string = "v4"
SERVICE_INFO_SPLITER string = "@@"
TIMESTAMP_HEADER string = "Timestamp"
SIGNATURE_HEADER string = "Spas-Signature"
)
type NamingResourceInjector struct {
}
func (n *NamingResourceInjector) doInject(resource RequestResource, ramContext RamContext, param map[string]string) {
param[NAMING_AK_FILED] = ramContext.AccessKey
if ramContext.EphemeralAccessKeyId {
param[SECURITY_TOKEN_HEADER] = ramContext.SecurityToken
}
secretKey := trySignatureWithV4(ramContext, param)
signatures := n.calculateSignature(resource, secretKey, ramContext)
for k, v := range signatures {
param[k] = v
}
}
func (n *NamingResourceInjector) calculateSignature(resource RequestResource, secretKey string, ramContext RamContext) map[string]string {
var result = make(map[string]string, 4)
signData := n.getSignData(n.getGroupedServiceName(resource))
signature, err := Sign(signData, secretKey)
if err != nil {
logger.Errorf("get v4 signatrue error: %v", err)
return result
}
result["signature"] = signature
result["data"] = signData
return result
}
func (n *NamingResourceInjector) getGroupedServiceName(resource RequestResource) string {
if strings.Contains(resource.resource, SERVICE_INFO_SPLITER) || resource.group == "" {
return resource.resource
}
return resource.group + SERVICE_INFO_SPLITER + resource.resource
}
func (n *NamingResourceInjector) getSignData(serviceName string) string {
if serviceName != "" {
return fmt.Sprintf("%d%s%s", time.Now().UnixMilli(), SERVICE_INFO_SPLITER, serviceName)
}
return fmt.Sprintf("%d", time.Now().UnixMilli())
}
type ConfigResourceInjector struct {
}
func (c *ConfigResourceInjector) doInject(resource RequestResource, ramContext RamContext, param map[string]string) {
param[CONFIG_AK_FILED] = ramContext.AccessKey
if ramContext.EphemeralAccessKeyId {
param[SECURITY_TOKEN_HEADER] = ramContext.SecurityToken
}
secretKey := trySignatureWithV4(ramContext, param)
signatures := c.calculateSignature(resource, secretKey, ramContext)
for k, v := range signatures {
param[k] = v
}
}
func (c *ConfigResourceInjector) calculateSignature(resource RequestResource, secretKey string, ramContext RamContext) map[string]string {
var result = make(map[string]string, 4)
resourceName := c.getResourceName(resource)
signHeaders := c.getSignHeaders(resourceName, secretKey)
for k, v := range signHeaders {
result[k] = v
}
return result
}
func (c *ConfigResourceInjector) getResourceName(resource RequestResource) string {
if resource.namespace != "" {
return resource.namespace + "+" + resource.group
} else {
return resource.group
}
}
func (c *ConfigResourceInjector) getSignHeaders(resource, secretKey string) map[string]string {
header := make(map[string]string, 4)
timeStamp := fmt.Sprintf("%d", time.Now().UnixMilli())
header[TIMESTAMP_HEADER] = timeStamp
if secretKey != "" {
var signature string
if strings.TrimSpace(resource) == "" {
signature = signWithHmacSha1Encrypt(timeStamp, secretKey)
} else {
signature = signWithHmacSha1Encrypt(resource+"+"+timeStamp, secretKey)
}
header[SIGNATURE_HEADER] = signature
}
return header
}
func trySignatureWithV4(ramContext RamContext, param map[string]string) string {
if ramContext.SignatureRegionId == "" {
return ramContext.SecretKey
}
signatureV4, err := finalSigningKeyStringWithDefaultInfo(ramContext.SecretKey, ramContext.SignatureRegionId)
if err != nil {
logger.Errorf("get v4 signatrue error: %v", err)
return ramContext.SecretKey
}
param[SIGNATURE_VERSION_HEADER] = SIGNATURE_VERSION_V4
return signatureV4
}

View File

@ -0,0 +1,98 @@
package security
import (
"github.com/stretchr/testify/assert"
"testing"
)
func Test_NamingResourceInjector_doInject(t *testing.T) {
namingResourceInjector := NamingResourceInjector{}
resource := BuildNamingResource("testNamespace", "testGroup", "testServiceName")
t.Run("test_doInject_v4_sts", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
SecurityToken: "testSecurityToken",
EphemeralAccessKeyId: true,
SignatureRegionId: "testSignatureRegionId",
}
param := map[string]string{}
namingResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[NAMING_AK_FILED], ramContext.AccessKey)
assert.Equal(t, param[SECURITY_TOKEN_HEADER], ramContext.SecurityToken)
assert.Equal(t, param[SIGNATURE_VERSION_HEADER], SIGNATURE_VERSION_V4)
assert.NotEmpty(t, param["signature"])
})
t.Run("test_doInject", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
}
param := map[string]string{}
namingResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[NAMING_AK_FILED], ramContext.AccessKey)
assert.Empty(t, param[SECURITY_TOKEN_HEADER])
assert.Empty(t, param[SIGNATURE_VERSION_HEADER])
assert.NotEmpty(t, param["signature"])
})
}
func Test_NamingResourceInjector_getGroupedServiceName(t *testing.T) {
namingResourceInjector := NamingResourceInjector{}
t.Run("test_getGroupedServiceName", func(t *testing.T) {
resource := BuildNamingResource("testNamespace", "testGroup", "testServiceName")
assert.Equal(t, namingResourceInjector.getGroupedServiceName(resource), "testGroup@@testServiceName")
})
t.Run("test_getGroupedServiceName_without_group", func(t *testing.T) {
resource := BuildNamingResource("testNamespace", "", "testServiceName")
assert.Equal(t, namingResourceInjector.getGroupedServiceName(resource), "testServiceName")
})
}
func Test_ConfigResourceInjector_doInject(t *testing.T) {
configResourceInjector := ConfigResourceInjector{}
resource := BuildConfigResource("testTenant", "testGroup", "testDataId")
t.Run("test_doInject_v4_sts", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
SecurityToken: "testSecurityToken",
EphemeralAccessKeyId: true,
SignatureRegionId: "testSignatureRegionId",
}
param := map[string]string{}
configResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[CONFIG_AK_FILED], ramContext.AccessKey)
assert.Equal(t, param[SECURITY_TOKEN_HEADER], ramContext.SecurityToken)
assert.Equal(t, param[SIGNATURE_VERSION_HEADER], SIGNATURE_VERSION_V4)
assert.NotEmpty(t, param[SIGNATURE_HEADER])
assert.NotEmpty(t, param[TIMESTAMP_HEADER])
})
t.Run("test_doInject", func(t *testing.T) {
ramContext := RamContext{
AccessKey: "testAccessKey",
SecretKey: "testSecretKey",
}
param := map[string]string{}
configResourceInjector.doInject(resource, ramContext, param)
assert.Equal(t, param[CONFIG_AK_FILED], ramContext.AccessKey)
assert.Empty(t, param[SECURITY_TOKEN_HEADER])
assert.Empty(t, param[SIGNATURE_VERSION_HEADER])
assert.NotEmpty(t, param[SIGNATURE_HEADER])
assert.NotEmpty(t, param[TIMESTAMP_HEADER])
})
}
func Test_ConfigResourceInjector_getResourceName(t *testing.T) {
configResourceInjector := ConfigResourceInjector{}
t.Run("test_getGroupedServiceName", func(t *testing.T) {
resource := BuildConfigResource("testTenant", "testGroup", "testDataId")
assert.Equal(t, configResourceInjector.getResourceName(resource), "testTenant+testGroup")
})
t.Run("test_getGroupedServiceName_without_group", func(t *testing.T) {
resource := BuildConfigResource("testTenant", "", "testDataId")
assert.Equal(t, configResourceInjector.getResourceName(resource), "testTenant+")
})
}

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