Compare commits

...

No commits in common. "0.1.0-beta" and "master" have entirely different histories.

227 changed files with 16742 additions and 13806 deletions

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

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

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

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

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

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

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

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

2
.gitignore vendored Normal file
View File

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

10
CONTRIBUTING.md Normal file
View File

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

63
Gopkg.lock generated
View File

@ -1,63 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/buger/jsonparser"
packages = ["."]
revision = "bf1c66bbce23153d89b23f8960071a680dbef54b"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
name = "github.com/go-errors/errors"
packages = ["."]
revision = "a6af135bd4e28680facf08a3d206b454abc877a4"
version = "v1.0.1"
[[projects]]
name = "github.com/golang/mock"
packages = ["gomock"]
revision = "9fa652df1129bef0e734c9cf9bf6dbae9ef3b9fa"
version = "1.3.1"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
version = "v0.8.1"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053"
version = "v1.3.0"
[[projects]]
branch = "master"
name = "github.com/toolkits/concurrent"
packages = ["semaphore"]
revision = "a4371d70e3e308db0d5f90c2b1f5efaa84add725"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "40dd585edc77fad2a9636d1fc439a840d18f8eca529eebbabb679d681f4950ed"
solver-name = "gps-cdcl"
solver-version = 1

View File

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

View File

@ -1,4 +1,3 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

22
NOTICE Normal file
View File

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

471
README.md
View File

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

389
README_CN.md Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,55 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
syntax = "proto3";
import "google/protobuf/any.proto";
option java_multiple_files = true;
option java_package = "com.alibaba.nacos.api.grpc.auto";
option go_package = "github.com/nacos-group/nacos-sdk-go/v2/api/grpc/auto;auto";
message Metadata {
string type = 3;
string clientIp = 8;
map<string, string> headers = 7;
}
message Payload {
Metadata metadata = 2;
google.protobuf.Any body = 3;
}
service RequestStream {
// build a streamRequest
rpc requestStream (Payload) returns (stream Payload) {
}
}
service Request {
// Sends a commonRequest
rpc request (Payload) returns (Payload) {
}
}
service BiRequestStream {
// Sends a commonRequest
rpc requestBiStream (stream Payload) returns (stream Payload) {
}
}

View File

@ -1,3 +1,29 @@
/*
The MIT License (MIT)
Copyright (c) 2014 streamrail
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cache
import (
@ -270,7 +296,7 @@ func (m ConcurrentMap) Keys() []string {
return keys
}
//Reviles ConcurrentMap "private" variables to json marshal.
// Reviles ConcurrentMap "private" variables to json marshal.
func (m ConcurrentMap) MarshalJSON() ([]byte, error) {
// Create a temporary map, which will hold all item spread across shards.
tmp := make(map[string]interface{})

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

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

View File

@ -1,77 +1,203 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cache
import (
"encoding/json"
"fmt"
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/common/util"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"syscall"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
)
func GetFileName(cacheKey string, cacheDir string) string {
var (
fileNotExistError = errors.New("file not exist")
)
func GetFileName(cacheKey, cacheDir string) string {
return cacheDir + string(os.PathSeparator) + cacheKey
}
func WriteServicesToFile(service model.Service, cacheDir string) {
util.MkdirIfNecessary(cacheDir)
sb, _ := json.Marshal(service)
domFileName := GetFileName(utils.GetServiceCacheKey(service.Name, service.Clusters), cacheDir)
func GetEncryptedDataKeyDir(cacheDir string) string {
return cacheDir + string(os.PathSeparator) + ENCRYPTED_DATA_KEY_FILE_NAME
}
err := ioutil.WriteFile(domFileName, sb, 0666)
func GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir string) string {
return GetEncryptedDataKeyDir(cacheDir) + string(os.PathSeparator) + cacheKey
}
func GetConfigFailOverContentFileName(cacheKey, cacheDir string) string {
return GetFileName(cacheKey, cacheDir) + FAILOVER_FILE_SUFFIX
}
func GetConfigFailOverEncryptedDataKeyFileName(cacheKey, cacheDir string) string {
return GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir) + FAILOVER_FILE_SUFFIX
}
func WriteServicesToFile(service *model.Service, cacheKey, cacheDir string) {
err := file.MkdirIfNecessary(cacheDir)
if err != nil {
log.Printf("[ERROR]:faild to write name cache:%s ,value:%s ,err:%s \n", domFileName, string(sb), err.Error())
logger.Errorf("mkdir cacheDir failed,cacheDir:%s,err:", cacheDir, err)
return
}
bytes, _ := json.Marshal(service)
domFileName := GetFileName(cacheKey, cacheDir)
err = os.WriteFile(domFileName, bytes, 0666)
if err != nil {
logger.Errorf("failed to write name cache:%s ,value:%s ,err:%v", domFileName, string(bytes), err)
}
}
func ReadServicesFromFile(cacheDir string) map[string]model.Service {
files, err := ioutil.ReadDir(cacheDir)
files, err := os.ReadDir(cacheDir)
if err != nil {
log.Printf("[ERROR]:read cacheDir:%s failed!err:%s \n", cacheDir, err.Error())
logger.Errorf("read cacheDir:%s failed!err:%+v", cacheDir, err)
return nil
}
serviceMap := map[string]model.Service{}
for _, f := range files {
fileName := GetFileName(f.Name(), cacheDir)
b, err := ioutil.ReadFile(fileName)
b, err := os.ReadFile(fileName)
if err != nil {
log.Printf("[ERROR]:failed to read name cache file:%s,err:%s! ", fileName, err.Error())
logger.Errorf("failed to read name cache file:%s,err:%v ", fileName, err)
continue
}
s := string(b)
service := utils.JsonToService(s)
service := util.JsonToService(s)
if service == nil {
continue
}
serviceMap[f.Name()] = *service
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
serviceMap[cacheKey] = *service
}
log.Printf("finish loading name cache, total: " + strconv.Itoa(len(files)))
logger.Infof("finish loading name cache, total: %s", strconv.Itoa(len(files)))
return serviceMap
}
func WriteConfigToFile(cacheKey string, cacheDir string, content string) {
util.MkdirIfNecessary(cacheDir)
fileName := GetFileName(cacheKey, cacheDir)
err := ioutil.WriteFile(fileName, []byte(content), 0666)
func WriteConfigToFile(cacheKey string, cacheDir string, content string) error {
err := file.MkdirIfNecessary(cacheDir)
if err != nil {
log.Printf("[ERROR]:faild to write config cache:%s ,value:%s ,err:%s \n", fileName, string(content), err.Error())
errMsg := fmt.Sprintf("make dir failed, dir path %s, err: %v.", cacheDir, err)
logger.Error(errMsg)
return errors.New(errMsg)
}
err = writeConfigToFile(GetFileName(cacheKey, cacheDir), content, ConfigContent)
if err != nil {
logger.Error(err)
return err
}
return nil
}
func WriteEncryptedDataKeyToFile(cacheKey string, cacheDir string, content string) error {
err := file.MkdirIfNecessary(GetEncryptedDataKeyDir(cacheDir))
if err != nil {
errMsg := fmt.Sprintf("make dir failed, dir path %s, err: %v.", cacheDir, err)
logger.Error(errMsg)
return errors.New(errMsg)
}
err = writeConfigToFile(GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir), content, ConfigEncryptedDataKey)
if err != nil {
logger.Error(err)
return err
}
return nil
}
func writeConfigToFile(fileName string, content string, fileType ConfigCachedFileType) error {
if len(strings.TrimSpace(content)) == 0 {
// delete config snapshot
if err := os.Remove(fileName); err != nil {
if err != syscall.ENOENT {
logger.Debug(fmt.Sprintf("no need to delete %s cache file, file path %s, file doesn't exist.", fileType, fileName))
return nil
}
errMsg := fmt.Sprintf("failed to delete %s cache file, file path %s, err:%v", fileType, fileName, err)
return errors.New(errMsg)
}
}
err := os.WriteFile(fileName, []byte(content), 0666)
if err != nil {
errMsg := fmt.Sprintf("failed to write %s cache file, file name: %s, value: %s, err:%v", fileType, fileName, content, err)
return errors.New(errMsg)
}
return nil
}
func ReadEncryptedDataKeyFromFile(cacheKey string, cacheDir string) (string, error) {
content, err := readConfigFromFile(GetConfigEncryptedDataKeyFileName(cacheKey, cacheDir), ConfigEncryptedDataKey)
if err != nil {
if errors.Is(err, fileNotExistError) {
logger.Warn(err)
return "", nil
}
}
return content, nil
}
func ReadConfigFromFile(cacheKey string, cacheDir string) (string, error) {
fileName := GetFileName(cacheKey, cacheDir)
b, err := ioutil.ReadFile(fileName)
return readConfigFromFile(GetFileName(cacheKey, cacheDir), ConfigEncryptedDataKey)
}
func readConfigFromFile(fileName string, fileType ConfigCachedFileType) (string, error) {
if !file.IsExistFile(fileName) {
errMsg := fmt.Sprintf("read cache file %s failed. cause file doesn't exist, file path: %s.", fileType, fileName)
return "", errors.Wrap(fileNotExistError, errMsg)
}
b, err := os.ReadFile(fileName)
if err != nil {
return "", errors.New(fmt.Sprintf("failed to read config cache file:%s,err:%s! ", fileName, err.Error()))
errMsg := fmt.Sprintf("get %s from cache failed, filePath:%s, error:%v ", fileType, fileName, err)
return "", errors.New(errMsg)
}
return string(b), nil
}
// GetFailover , get failover content
func GetFailover(key, dir string) string {
filePath := GetConfigFailOverContentFileName(key, dir)
return getFailOverConfig(filePath, ConfigContent)
}
func GetFailoverEncryptedDataKey(key, dir string) string {
filePath := GetConfigFailOverEncryptedDataKeyFileName(key, dir)
return getFailOverConfig(filePath, ConfigEncryptedDataKey)
}
func getFailOverConfig(filePath string, fileType ConfigCachedFileType) string {
if !file.IsExistFile(filePath) {
errMsg := fmt.Sprintf("read %s failed. cause file doesn't exist, file path: %s.", fileType, filePath)
logger.Warn(errMsg)
return ""
}
logger.Warnf("reading failover %s from path:%s", fileType, filePath)
fileContent, err := os.ReadFile(filePath)
if err != nil {
logger.Errorf("fail to read failover %s from %s", fileType, filePath)
return ""
}
return string(fileContent)
}

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

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

View File

@ -1,79 +1,128 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package clients
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// 创建配置相关的客户端
func CreateConfigClient(properties map[string]interface{}) (iClient config_client.IConfigClient,
err error) {
nacosClient, errSetConfig := setConfig(properties)
if errSetConfig != nil {
err = errSetConfig
return
}
nacosClient.SetHttpAgent(&http_agent.HttpAgent{})
config, errNew := config_client.NewConfigClient(nacosClient)
if errNew != nil {
err = errNew
return
}
iClient = &config
return
// CreateConfigClient use to create config client
func CreateConfigClient(properties map[string]interface{}) (iClient config_client.IConfigClient, err error) {
param := getConfigParam(properties)
return NewConfigClient(param)
}
// 创建服务发现相关的客户端
// CreateNamingClient use to create a nacos naming client
func CreateNamingClient(properties map[string]interface{}) (iClient naming_client.INamingClient, err error) {
nacosClient, errSetConfig := setConfig(properties)
if errSetConfig != nil {
err = errSetConfig
param := getConfigParam(properties)
return NewNamingClient(param)
}
func NewConfigClient(param vo.NacosClientParam) (iClient config_client.IConfigClient, err error) {
nacosClient, err := setConfig(param)
if err != nil {
return
}
nacosClient.SetHttpAgent(&http_agent.HttpAgent{})
naming, errNew := naming_client.NewNamingClient(nacosClient)
if errNew != nil {
err = errNew
config, err := config_client.NewConfigClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}
iClient = &naming
iClient = config
return
}
func setConfig(properties map[string]interface{}) (iClient nacos_client.INacosClient, err error) {
client := nacos_client.NacosClient{}
func NewNamingClient(param vo.NacosClientParam) (iClient naming_client.INamingClient, err error) {
nacosClient, err := setConfig(param)
if err != nil {
return
}
naming, err := naming_client.NewNamingClientWithRamCredentialProvider(nacosClient, param.RamCredentialProvider)
if err != nil {
return
}
iClient = naming
return
}
func getConfigParam(properties map[string]interface{}) (param vo.NacosClientParam) {
if clientConfigTmp, exist := properties[constant.KEY_CLIENT_CONFIG]; exist {
if clientConfig, ok := clientConfigTmp.(constant.ClientConfig); ok {
err = client.SetClientConfig(clientConfig)
if err != nil {
return
}
param.ClientConfig = &clientConfig
}
} else {
_ = client.SetClientConfig(constant.ClientConfig{
TimeoutMs: 30 * 1000,
ListenInterval: 10 * 1000,
BeatInterval: 5 * 1000,
})
}
// 设置 serverConfig
if serverConfigTmp, exist := properties[constant.KEY_SERVER_CONFIGS]; exist {
if serverConfigs, ok := serverConfigTmp.([]constant.ServerConfig); ok {
err = client.SetServerConfig(serverConfigs)
if err != nil {
return
}
param.ServerConfigs = serverConfigs
}
} else {
err = errors.New("server configs not found in properties")
return
}
iClient = &client
return
}
func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, err error) {
client := &nacos_client.NacosClient{}
if param.ClientConfig == nil {
// default clientConfig
_ = client.SetClientConfig(constant.ClientConfig{
TimeoutMs: 10 * 1000,
BeatInterval: 5 * 1000,
})
} else {
err = client.SetClientConfig(*param.ClientConfig)
if err != nil {
return nil, err
}
}
if len(param.ServerConfigs) == 0 {
clientConfig, _ := client.GetClientConfig()
if len(clientConfig.Endpoint) <= 0 {
err = errors.New("server configs not found in properties")
return nil, err
}
_ = client.SetServerConfig(nil)
} else {
for i := range param.ServerConfigs {
if param.ServerConfigs[i].Port == 0 {
param.ServerConfigs[i].Port = 8848
}
if param.ServerConfigs[i].GrpcPort == 0 {
param.ServerConfigs[i].GrpcPort = param.ServerConfigs[i].Port + constant.RpcPortOffset
}
}
err = client.SetServerConfig(param.ServerConfigs)
if err != nil {
return nil, err
}
}
if _, _err := client.GetHttpAgent(); _err != nil {
if clientCfg, err := client.GetClientConfig(); err == nil {
_ = client.SetHttpAgent(&http_agent.HttpAgent{TlsConfig: clientCfg.TLSCfg})
}
}
iClient = client
return
}

View File

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

View File

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

View File

@ -1,43 +1,70 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config_client
import (
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-08 10:03
**/
//go:generate mockgen -destination ../../mock/mock_config_client_interface.go -package mock -source=./config_client_interface.go
type IConfigClient interface {
// 获取配置
// GetConfig use to get config from nacos server
// dataId require
// group require
// tenant ==>nacos.namespace optional
GetConfig(param vo.ConfigParam) (string, error)
// 发布配置
// PublishConfig use to publish config to nacos server
// dataId require
// group require
// content require
// tenant ==>nacos.namespace optional
PublishConfig(param vo.ConfigParam) (bool, error)
// 删除配置
// DeleteConfig use to delete config
// dataId require
// group require
// tenant ==>nacos.namespace optional
DeleteConfig(param vo.ConfigParam) (bool, error)
// 监听配置
// ListenConfig use to listen config change,it will callback OnChange() when config change
// dataId require
// group require
// onchange require
// tenant ==>nacos.namespace optional
ListenConfig(params vo.ConfigParam) (err error)
//CancelListenConfig use to cancel listen config change
// dataId require
// group require
// tenant ==>nacos.namespace optional
ListenConfig(params vo.ConfigParam) (err error)
CancelListenConfig(params vo.ConfigParam) (err error)
// SearchConfig use to search nacos config
// search require search=accurate--精确搜索 search=blur--模糊搜索
// group option
// dataId option
// tenant ==>nacos.namespace optional
// pageNo option,default is 1
// pageSize option,default is 10
SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error)
// CloseClient Close the GRPC client
CloseClient()
}

View File

@ -1,49 +1,64 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config_client
import (
"fmt"
"github.com/golang/mock/gomock"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/mock"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/stretchr/testify/assert"
"net/http"
"context"
"errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-16 21:01
**/
var serverConfigWithOptions = constant.NewServerConfig("127.0.0.1", 8848)
var clientConfigTest = constant.ClientConfig{
TimeoutMs: 20000,
ListenInterval: 10000,
BeatInterval: 10000,
}
var clientConfigWithOptions = constant.NewClientConfig(
constant.WithTimeoutMs(10*1000),
constant.WithBeatInterval(2*1000),
constant.WithNotLoadCacheAtStart(true),
constant.WithAccessKey("LTAxxx"),
constant.WithSecretKey("EdPxxx"),
constant.WithOpenKMS(true),
constant.WithKMSVersion(constant.KMSv1),
constant.WithRegionId("cn-hangzhou"),
)
var serverConfigTest = constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "console.nacos.io",
}
var clientTLsConfigWithOptions = constant.NewClientConfig(
constant.WithTimeoutMs(10*1000),
constant.WithBeatInterval(2*1000),
constant.WithNotLoadCacheAtStart(true),
var configParamMapTest = map[string]string{
"dataId": "dataId",
"group": "group",
}
var configParamTest = vo.ConfigParam{
DataId: "dataId",
Group: "group",
}
/*constant.WithTLS(constant.TLSConfig{
Enable: true,
TrustAll: false,
CaFile: "mse-nacos-ca.cer",
}),*/
)
var localConfigTest = vo.ConfigParam{
DataId: "dataId",
@ -51,136 +66,183 @@ var localConfigTest = vo.ConfigParam{
Content: "content",
}
var localConfigMapTest = map[string]string{
"dataId": "dataId",
"group": "group",
"content": "content",
}
var headerTest = map[string][]string{
"Content-Type": {"application/x-www-form-urlencoded"},
}
var serverConfigsTest = []constant.ServerConfig{serverConfigTest}
var httpAgentTest = mock.MockIHttpAgent{}
func cretateConfigClientTest() ConfigClient {
func createConfigClientTest() *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(&http_agent.HttpAgent{})
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
func cretateConfigClientHttpTest(mockHttpAgent http_agent.IHttpAgent) ConfigClient {
func createConfigClientTestTls() *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockHttpAgent)
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientTLsConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
// sync
func Test_SyncWithoutClientConfig(t *testing.T) {
client := cretateConfigClientTest()
_, _, _, err := client.sync()
assert.NotNil(t, err)
func createConfigClientCommon() *ConfigClient {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
func Test_SyncWithoutServerConfig(t *testing.T) {
client := cretateConfigClientTest()
_ = client.SetClientConfig(clientConfigTest)
_, _, _, err := client.sync()
assert.NotNil(t, err)
func createConfigClientForKms() *ConfigClient {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxyForUsingLocalDiskCache{}
return client
}
func Test_SyncWithoutHttpAgent(t *testing.T) {
client := cretateConfigClientTest()
_ = client.SetServerConfig(serverConfigsTest)
_ = client.SetClientConfig(clientConfigTest)
_, _, _, err := client.sync()
assert.NotNil(t, err)
type MockConfigProxyForUsingLocalDiskCache struct {
MockConfigProxy
}
func Test_Sync(t *testing.T) {
client := cretateConfigClientTest()
_ = client.SetClientConfig(clientConfigTest)
_ = client.SetServerConfig(serverConfigsTest)
_ = client.SetHttpAgent(&httpAgentTest)
_, _, _, err := client.sync()
assert.Nil(t, err)
func (m *MockConfigProxyForUsingLocalDiskCache) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
return nil, errors.New("mock err for using localCache")
}
type MockConfigProxy struct {
}
func (m *MockConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
cacheKey := util.GetConfigCacheKey(dataId, group, tenant)
if IsLimited(cacheKey) {
return nil, errors.New("request is limited")
}
return &rpc_response.ConfigQueryResponse{Content: "hello world", Response: &rpc_response.Response{Success: true}}, nil
}
func (m *MockConfigProxy) searchConfigProxy(param vo.SearchConfigParam, tenant, accessKey, secretKey string) (*model.ConfigPage, error) {
return &model.ConfigPage{TotalCount: 1}, nil
}
func (m *MockConfigProxy) requestProxy(rpcClient *rpc.RpcClient, request rpc_request.IRequest, timeoutMills uint64) (rpc_response.IResponse, error) {
return &rpc_response.MockResponse{Response: &rpc_response.Response{Success: true}}, nil
}
func (m *MockConfigProxy) createRpcClient(ctx context.Context, taskId string, client *ConfigClient) *rpc.RpcClient {
return &rpc.RpcClient{}
}
func (m *MockConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient {
return &rpc.RpcClient{}
}
func Test_GetConfig(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: localConfigTest.DataId,
Group: localConfigTest.Group})
assert.Nil(t, err)
assert.Equal(t, "hello world!222222", content)
assert.Equal(t, "hello world", content)
}
func Test_GetConfigWithErrorResponse(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
_, err := client.GetConfig(configParamTest)
assert.NotNil(t, err)
}
func Test_GetConfigWithCache(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "content"), nil)
content, err := client.GetConfig(configParamTest)
func Test_SearchConfig(t *testing.T) {
client := createConfigClientTest()
_, _ = client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
Content: "hello world"})
configPage, err := client.SearchConfig(vo.SearchConfigParam{
Search: "accurate",
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 10,
})
assert.Nil(t, err)
assert.Equal(t, "content", content)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodGet),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
content, err = client.GetConfig(configParamTest)
assert.Nil(t, err)
assert.Equal(t, "content", content)
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 := cretateConfigClientTest()
client := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -189,20 +251,10 @@ func Test_PublishConfigWithoutDataId(t *testing.T) {
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutGroup(t *testing.T) {
client := cretateConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "",
Content: "content",
})
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutContent(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group",
Content: "",
})
@ -211,59 +263,25 @@ func Test_PublishConfigWithoutContent(t *testing.T) {
func Test_PublishConfig(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group",
Content: "hello world2!"})
SrcUser: "nacos-client-go",
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
}
func Test_PublishConfigWithErrorResponse(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodPost),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(localConfigMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
success, err := client.PublishConfig(localConfigTest)
assert.NotNil(t, err)
assert.True(t, !success)
}
func Test_PublishConfigWithErrorResponse_200(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodPost),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(localConfigMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "false"), nil)
success, err := client.PublishConfig(localConfigTest)
assert.NotNil(t, err)
assert.True(t, !success)
}
// DeleteConfig
func Test_DeleteConfig(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group",
Content: "hello world!"})
@ -271,51 +289,15 @@ func Test_DeleteConfig(t *testing.T) {
assert.True(t, success)
success, err = client.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
DataId: localConfigTest.DataId,
Group: "group"})
assert.Nil(t, err)
assert.True(t, success)
}
func Test_DeleteConfigWithErrorResponse_200(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodDelete),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "false"), nil)
success, err := client.DeleteConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithErrorResponse_401(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodDelete),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
success, err := client.DeleteConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutDataId(t *testing.T) {
client := cretateConfigClientTest()
client := createConfigClientTest()
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -324,296 +306,106 @@ func Test_DeleteConfigWithoutDataId(t *testing.T) {
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutGroup(t *testing.T) {
client := cretateConfigClientTest()
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
Group: "",
})
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
// ListenConfig
func TestListenConfig(t *testing.T) {
client := cretateConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!"})
assert.Nil(t, err)
assert.Equal(t, true, success)
content := ""
err = client.ListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
content = data
},
})
err = client.ListenConfig(vo.ConfigParam{
DataId: "abc",
Group: "DEFAULT_GROUP",
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
},
})
time.Sleep(5 * time.Second)
assert.Equal(t, "hello world!", content)
success, err = client.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "abc"})
assert.Nil(t, err)
assert.Equal(t, true, success)
time.Sleep(10 * time.Second)
assert.Equal(t, "abc", content)
}
// listenConfigTask
func Test_listenConfigTask_NoChange(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client := cretateConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Post(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs/listener"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, ""), nil)
changeCount := 0
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent, vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
OnChange: func(namespace, group, dataId, data string) {
changeCount = changeCount + 1
}})
assert.Equal(t, 0, changeCount)
}
func Test_listenConfigTask_Change_WithTenant(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
mockHttpAgent.EXPECT().Post(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs/listener"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "dataId%02group%02tenant%01"), nil)
mockHttpAgent.EXPECT().Get(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"dataId": "dataId",
"group": "group",
"tenant": "tenant",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "content2"), nil)
_ = client.SetHttpAgent(mockHttpAgent)
_ = client.SetClientConfig(clientConfigTest)
_ = client.SetServerConfig(serverConfigsTest)
configData := ""
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent, vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
OnChange: func(namespace, group, dataId, data string) {
configData = data
}})
assert.Equal(t, "content2", configData)
}
func Test_listenConfigTask_Change_NoTenant(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
mockHttpAgent.EXPECT().Post(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs/listener"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "dataId%02group%01"), nil)
mockHttpAgent.EXPECT().Get(
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(map[string]string{
"dataId": "dataId",
"group": "group",
}),
).Times(1).Return(http_agent.FakeHttpResponse(200, "content2"), nil)
_ = client.SetHttpAgent(mockHttpAgent)
_ = client.SetClientConfig(clientConfigTest)
_ = client.SetServerConfig(serverConfigsTest)
configData := ""
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent, vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
OnChange: func(namespace, group, dataId, data string) {
configData = data
},
})
assert.Equal(t, "content2", configData)
}
func Test_listenConfigTaskWithoutLocalConfigs(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
client.listenConfigTask(clientConfigTest, serverConfigsTest, mockHttpAgent,
vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "content",
func TestListen(t *testing.T) {
t.Run("TestListenConfig", func(t *testing.T) {
client := createConfigClientTest()
err := client.ListenConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
})
//assert.Equal(t, false, client.listening)
assert.Nil(t, err)
})
// ListenConfig no dataId
t.Run("TestListenConfigNoDataId", func(t *testing.T) {
listenConfigParam := vo.ConfigParam{
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
client := createConfigClientTest()
err := client.ListenConfig(listenConfigParam)
assert.Error(t, err)
})
}
// listen
// CancelListenConfig
func TestCancelListenConfig(t *testing.T) {
//Multiple listeners listen for different configurations, cancel one
t.Run("TestMultipleListenersCancelOne", func(t *testing.T) {
client := createConfigClientTest()
var err error
listenConfigParam := vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
func Test_listen(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
path := "http://console.nacos.io:80/nacos/v1/cs/configs/listener"
param := map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
listenConfigParam1 := vo.ConfigParam{
DataId: localConfigTest.DataId + "1",
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
_ = client.ListenConfig(listenConfigParam)
_ = client.ListenConfig(listenConfigParam1)
err = client.CancelListenConfig(listenConfigParam)
assert.Nil(t, err)
})
}
type MockAccessKeyCredentialProvider struct {
accessKey string
secretKey string
signatureRegionId string
}
func (provider *MockAccessKeyCredentialProvider) MatchProvider() bool {
return true
}
func (provider *MockAccessKeyCredentialProvider) Init() error {
return nil
}
func (provider *MockAccessKeyCredentialProvider) GetCredentialsForNacosClient() security.RamContext {
ramContext := security.RamContext{
AccessKey: provider.accessKey,
SecretKey: provider.secretKey,
SignatureRegionId: "",
}
changedString := "dataId%02group%02tenant%01"
mockHttpAgent.EXPECT().Post(
gomock.Eq(path),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(param),
).Times(1).Return(http_agent.FakeHttpResponse(200, changedString), nil)
_ = client.SetHttpAgent(mockHttpAgent)
changed, err := listen(mockHttpAgent, path, clientConfigTest.TimeoutMs, clientConfigTest.ListenInterval, param)
assert.Equal(t, changed, changedString)
assert.Nil(t, err)
return ramContext
}
func Test_listenWithErrorResponse_401(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
client := cretateConfigClientTest()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
path := "http://console.nacos.io:80/nacos/v1/cs/configs/listener"
param := map[string]string{
"Listening-Configs": "dataIdgroup9a0364b9e99bb480dd25e1f0284c8555tenant",
func Test_ConfigClientWithProvider(t *testing.T) {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
clientConfigWithOptions.AccessKey = ""
clientConfigWithOptions.SecretKey = ""
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
provider := &MockAccessKeyCredentialProvider{
accessKey: "LTAxxx",
secretKey: "EdPxxx",
}
mockHttpAgent.EXPECT().Post(
gomock.Eq(path),
gomock.AssignableToTypeOf(headerTest),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(param),
).Times(1).Return(http_agent.FakeHttpResponse(401, "no auth"), nil)
client, _ := NewConfigClientWithRamCredentialProvider(&nc, provider)
client.configProxy = &MockConfigProxy{}
success, err := client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: "hello world"})
_ = client.SetHttpAgent(mockHttpAgent)
_, err := listen(mockHttpAgent, path, clientConfigTest.TimeoutMs, clientConfigTest.ListenInterval, param)
assert.NotNil(t, err)
}
// AddConfigToListen
func Test_AddConfigToListenWithNotListening(t *testing.T) {
client := cretateConfigClientTest()
configs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
Content: "content",
}}
err := client.AddConfigToListen(configs)
assert.NotNil(t, err)
}
func Test_AddConfigToListenWithRepeatAdd(t *testing.T) {
client := cretateConfigClientTest()
configs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}}
addConfigs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}, {
DataId: "dataId2",
Group: "group",
}, {
DataId: "dataId3",
Group: "group",
}}
client.localConfigs = configs
err := client.AddConfigToListen(addConfigs)
assert.Nil(t, err)
assert.Equal(t, addConfigs, client.localConfigs)
}
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group})
func Test_AddConfigToListen(t *testing.T) {
client := cretateConfigClientTest()
configs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}}
addConfigs := []vo.ConfigParam{
{
DataId: "dataId2",
Group: "group",
}, {
DataId: "dataId3",
Group: "group",
}}
resultConfigs := []vo.ConfigParam{{
DataId: "dataId",
Group: "group",
}, {
DataId: "dataId2",
Group: "group",
}, {
DataId: "dataId3",
Group: "group",
}}
client.localConfigs = configs
err := client.AddConfigToListen(addConfigs)
assert.Nil(t, err)
assert.Equal(t, resultConfigs, client.localConfigs)
assert.Equal(t, "hello world", content)
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config_client
import (
"strconv"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
)
type ConfigConnectionEventListener struct {
client *ConfigClient
taskId string
}
func NewConfigConnectionEventListener(client *ConfigClient, taskId string) *ConfigConnectionEventListener {
return &ConfigConnectionEventListener{
client: client,
taskId: taskId,
}
}
func (c *ConfigConnectionEventListener) OnConnected() {
logger.Info("[ConfigConnectionEventListener] connect to config server for taskId: " + c.taskId)
if c.client != nil {
c.client.asyncNotifyListenConfig()
}
}
func (c *ConfigConnectionEventListener) OnDisConnect() {
logger.Info("[ConfigConnectionEventListener] disconnect from config server for taskId: " + c.taskId)
if c.client != nil {
taskIdInt, err := strconv.Atoi(c.taskId)
if err != nil {
logger.Errorf("[ConfigConnectionEventListener] parse taskId error: %v", err)
return
}
items := c.client.cacheMap.Items()
for key, v := range items {
if data, ok := v.(cacheData); ok {
if data.taskId == taskIdInt {
data.isSyncWithServer = false
c.client.cacheMap.Set(key, data)
}
}
}
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config_client
import (
"context"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/stretchr/testify/assert"
)
func TestNewConfigConnectionEventListener(t *testing.T) {
client := &ConfigClient{}
taskId := "123"
listener := NewConfigConnectionEventListener(client, taskId)
assert.Equal(t, client, listener.client)
assert.Equal(t, taskId, listener.taskId)
}
func TestOnDisConnectWithMock(t *testing.T) {
client := &ConfigClient{
cacheMap: cache.NewConcurrentMap(),
}
data1 := cacheData{
dataId: "dataId1",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
data2 := cacheData{
dataId: "dataId2",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
data3 := cacheData{
dataId: "dataId3",
group: "group2",
tenant: "",
taskId: 2,
isSyncWithServer: true,
}
key1 := util.GetConfigCacheKey(data1.dataId, data1.group, data1.tenant)
key2 := util.GetConfigCacheKey(data2.dataId, data2.group, data2.tenant)
key3 := util.GetConfigCacheKey(data3.dataId, data3.group, data3.tenant)
client.cacheMap.Set(key1, data1)
client.cacheMap.Set(key2, data2)
client.cacheMap.Set(key3, data3)
listener := NewConfigConnectionEventListener(client, "1")
listener.OnDisConnect()
item1, _ := client.cacheMap.Get(key1)
item2, _ := client.cacheMap.Get(key2)
item3, _ := client.cacheMap.Get(key3)
updatedData1 := item1.(cacheData)
updatedData2 := item2.(cacheData)
updatedData3 := item3.(cacheData)
assert.False(t, updatedData1.isSyncWithServer, "dataId1 should be marked as not sync")
assert.False(t, updatedData2.isSyncWithServer, "dataId2 should be marked as not sync")
assert.True(t, updatedData3.isSyncWithServer, "dataId3 should be marked as sync")
}
func TestOnConnectedWithMock(t *testing.T) {
listenChan := make(chan struct{}, 1)
client := &ConfigClient{
listenExecute: listenChan,
}
listener := NewConfigConnectionEventListener(client, "1")
listener.OnConnected()
time.Sleep(100 * time.Millisecond)
select {
case <-listenChan:
assert.True(t, true, "asyncNotifyListenConfig should be called")
default:
t.Fatalf("asyncNotifyListenConfig should be called but not")
}
}
type MockRpcClientForListener struct {
requestCalled rpc_request.IRequest
}
func (m *MockRpcClientForListener) Request(request rpc_request.IRequest) (rpc_response.IResponse, error) {
m.requestCalled = request
return &rpc_response.ConfigChangeBatchListenResponse{
Response: &rpc_response.Response{
ResultCode: 200,
},
ChangedConfigs: []model.ConfigContext{},
}, nil
}
func TestReconnectionFlow(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mockRpc := &MockRpcClientForListener{}
listenChan := make(chan struct{}, 1)
client := &ConfigClient{
ctx: ctx,
configProxy: &MockConfigProxy{},
cacheMap: cache.NewConcurrentMap(),
listenExecute: listenChan,
}
done := make(chan bool)
go func() {
for {
select {
case <-listenChan:
mockRpc.Request(&rpc_request.ConfigBatchListenRequest{})
done <- true
case <-ctx.Done():
return
}
}
}()
data1 := cacheData{
dataId: "dataId1",
group: "group1",
tenant: "",
taskId: 1,
isSyncWithServer: true,
}
key1 := util.GetConfigCacheKey(data1.dataId, data1.group, data1.tenant)
client.cacheMap.Set(key1, data1)
listener := NewConfigConnectionEventListener(client, "1")
initialData, _ := client.cacheMap.Get(key1)
assert.True(t, initialData.(cacheData).isSyncWithServer, "initial data should be sync with server")
listener.OnDisConnect()
afterDisconnectData, _ := client.cacheMap.Get(key1)
assert.False(t, afterDisconnectData.(cacheData).isSyncWithServer, "disconnect should set isSyncWithServer to false")
listener.OnConnected()
select {
case <-done:
case <-time.After(1 * time.Second):
t.Fatalf("wait for done timeout")
}
assert.NotNil(t, mockRpc.requestCalled, "should call request")
_, ok := mockRpc.requestCalled.(*rpc_request.ConfigBatchListenRequest)
assert.True(t, ok, "should be a ConfigBatchListenRequest")
}

View File

@ -1,65 +1,232 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config_client
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/common/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"context"
"encoding/json"
"net/http"
"strings"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/pkg/errors"
)
type ConfigProxy struct {
nacosServer nacos_server.NacosServer
nacosServer *nacos_server.NacosServer
clientConfig constant.ClientConfig
}
func NewConfigProxy(serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent) (ConfigProxy, error) {
func NewConfigProxy(ctx context.Context, serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent) (IConfigProxy, error) {
return NewConfigProxyWithRamCredentialProvider(ctx, serverConfig, clientConfig, httpAgent, nil)
}
func NewConfigProxyWithRamCredentialProvider(ctx context.Context, serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent, provider security.RamCredentialProvider) (IConfigProxy, error) {
proxy := ConfigProxy{}
var err error
proxy.nacosServer, err = nacos_server.NewNacosServer(serverConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint)
return proxy, err
proxy.nacosServer, err = nacos_server.NewNacosServerWithRamCredentialProvider(ctx, serverConfig, clientConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint, nil, provider)
proxy.clientConfig = clientConfig
return &proxy, err
}
func (cp *ConfigProxy) GetConfigProxy(param vo.ConfigParam, tenant string) (string, error) {
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) injectCommHeader(param map[string]string) {
now := strconv.FormatInt(util.CurrentMillis(), 10)
param[constant.CLIENT_APPNAME_HEADER] = cp.clientConfig.AppName
param[constant.CLIENT_REQUEST_TS_HEADER] = now
param[constant.CLIENT_REQUEST_TOKEN_HEADER] = util.Md5(now + cp.clientConfig.AppKey)
param[constant.EX_CONFIG_INFO] = "true"
param[constant.CHARSET_KEY] = "utf-8"
}
func (cp *ConfigProxy) searchConfigProxy(param vo.SearchConfigParam, tenant, accessKey, secretKey string) (*model.ConfigPage, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
}
result, err := cp.nacosServer.ReqApi(constant.CONFIG_PATH, params, http.MethodGet)
return result, err
}
func (cp *ConfigProxy) PublishConfigProxy(param vo.ConfigParam, tenant string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
if _, ok := params["group"]; !ok {
params["group"] = ""
}
result, err := cp.nacosServer.ReqApi(constant.CONFIG_PATH, params, http.MethodPost)
if _, ok := params["dataId"]; !ok {
params["dataId"] = ""
}
var headers = map[string]string{}
var version = "v2"
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.PublishConfig] publish config failed:" + err.Error())
if len(tenant) > 0 {
params["namespaceId"] = params["tenant"]
}
params["groupName"] = params["group"]
result, err = cp.nacosServer.ReqConfigApi("/v3/admin/cs/config/list", params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
if err != nil {
return nil, err
}
version = "v3"
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
var configPage model.ConfigPage
if version == "v2" {
err = json.Unmarshal([]byte(result), &configPage)
} else {
return false, errors.New("[client.PublishConfig] publish config failed:" + string(result))
var configPageResult model.ConfigPageResult
err = json.Unmarshal([]byte(result), &configPageResult)
configPage = configPageResult.Data
}
if err != nil {
return nil, err
}
return &configPage, nil
}
func (cp *ConfigProxy) DeleteConfigProxy(param vo.ConfigParam, tenant string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
func (cp *ConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
if group == "" {
group = constant.DEFAULT_GROUP
}
result, err := cp.nacosServer.ReqApi(constant.CONFIG_PATH, params, http.MethodDelete)
configQueryRequest := rpc_request.NewConfigQueryRequest(group, dataId, tenant)
configQueryRequest.Headers["notify"] = strconv.FormatBool(notify)
cacheKey := util.GetConfigCacheKey(dataId, group, tenant)
// use the same key of config file as the limit checker's key
if IsLimited(cacheKey) {
// return error when check limited
return nil, errors.New("ConfigQueryRequest is limited")
}
iResponse, err := cp.requestProxy(cp.getRpcClient(client), configQueryRequest, timeout)
if err != nil {
return false, errors.New("[client.DeleteConfig] deleted config failed:" + err.Error())
return nil, err
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
} else {
return false, errors.New("[client.DeleteConfig] deleted config failed: " + string(result))
response, ok := iResponse.(*rpc_response.ConfigQueryResponse)
if !ok {
return nil, errors.New("ConfigQueryRequest returns type error")
}
if response.IsSuccess() {
cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, response.Content)
cache.WriteEncryptedDataKeyToFile(cacheKey, cp.clientConfig.CacheDir, response.EncryptedDataKey)
if response.ContentType == "" {
response.ContentType = "text"
}
return response, nil
}
if response.GetErrorCode() == 300 {
cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, "")
cache.WriteEncryptedDataKeyToFile(cacheKey, cp.clientConfig.CacheDir, "")
response.SetSuccess(true)
return response, nil
}
if response.GetErrorCode() == 400 {
logger.Errorf(
"[config_rpc_client] [sub-server-error] get server config being modified concurrently, dataId=%s, group=%s, "+
"tenant=%s", dataId, group, tenant)
return nil, errors.New("data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant)
}
if response.GetErrorCode() > 0 {
logger.Errorf("[config_rpc_client] [sub-server-error] dataId=%s, group=%s, tenant=%s, code=%+v", dataId, group,
tenant, response)
}
return response, nil
}
func appName(client *ConfigClient) string {
if clientConfig, err := client.GetClientConfig(); err == nil {
appName := clientConfig.AppName
return appName
}
return "unknown"
}
func (cp *ConfigProxy) createRpcClient(ctx context.Context, taskId string, client *ConfigClient) *rpc.RpcClient {
labels := map[string]string{
constant.LABEL_SOURCE: constant.LABEL_SOURCE_SDK,
constant.LABEL_MODULE: constant.LABEL_MODULE_CONFIG,
constant.APPNAME_HEADER: appName(client),
"taskId": taskId,
}
iRpcClient, _ := rpc.CreateClient(ctx, "config-"+taskId+"-"+client.uid, rpc.GRPC, labels, cp.nacosServer, &cp.clientConfig.TLSCfg, cp.clientConfig.AppConnLabels)
rpcClient := iRpcClient.GetRpcClient()
if rpcClient.IsInitialized() {
rpcClient.RegisterServerRequestHandler(func() rpc_request.IRequest {
// TODO fix the group/dataId empty problem
return rpc_request.NewConfigChangeNotifyRequest("", "", "")
}, &ConfigChangeNotifyRequestHandler{client: client})
configListener := NewConfigConnectionEventListener(client, taskId)
rpcClient.RegisterConnectionListener(configListener)
rpcClient.Tenant = cp.clientConfig.NamespaceId
rpcClient.Start()
}
return rpcClient
}
func (cp *ConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient {
return cp.createRpcClient(client.ctx, "0", client)
}
type ConfigChangeNotifyRequestHandler struct {
client *ConfigClient
}
func (c *ConfigChangeNotifyRequestHandler) Name() string {
return "ConfigChangeNotifyRequestHandler"
}
func (c *ConfigChangeNotifyRequestHandler) RequestReply(request rpc_request.IRequest, rpcClient *rpc.RpcClient) rpc_response.IResponse {
configChangeNotifyRequest, ok := request.(*rpc_request.ConfigChangeNotifyRequest)
if !ok {
return nil
}
logger.Infof("%s [server-push] config changed. dataId=%s, group=%s,tenant=%s", rpcClient.Name(),
configChangeNotifyRequest.DataId, configChangeNotifyRequest.Group, configChangeNotifyRequest.Tenant)
cacheKey := util.GetConfigCacheKey(configChangeNotifyRequest.DataId, configChangeNotifyRequest.Group,
configChangeNotifyRequest.Tenant)
data, ok := c.client.cacheMap.Get(cacheKey)
if !ok {
return nil
}
cData := data.(cacheData)
cData.isSyncWithServer = false
c.client.cacheMap.Set(cacheKey, cData)
c.client.asyncNotifyListenConfig()
return &rpc_response.NotifySubscriberResponse{
Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS},
}
}

View File

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

View File

@ -0,0 +1,56 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config_client
import (
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"golang.org/x/time/rate"
)
type rateLimiterCheck struct {
rateLimiterCache cache.ConcurrentMap // cache
mux sync.Mutex
}
var checker rateLimiterCheck
func init() {
checker = rateLimiterCheck{
rateLimiterCache: cache.NewConcurrentMap(),
mux: sync.Mutex{},
}
}
// IsLimited return true when request is limited
func IsLimited(checkKey string) bool {
checker.mux.Lock()
defer checker.mux.Unlock()
var limiter *rate.Limiter
lm, exist := checker.rateLimiterCache.Get(checkKey)
if !exist {
// define a new limiter,allow 5 times per second,and reserve stock is 5.
limiter = rate.NewLimiter(rate.Limit(5), 5)
checker.rateLimiterCache.Set(checkKey, limiter)
} else {
limiter = lm.(*rate.Limiter)
}
add := time.Now().Add(time.Second)
return !limiter.AllowN(add, 1)
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package config_client
import (
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
func TestLimiter(t *testing.T) {
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "default-group",
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
for i := 0; i <= 10; i++ {
content, err := client.GetConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "default-group"})
if i > 4 {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
assert.Equal(t, "hello world", content)
}
}
}

View File

@ -1,23 +1,31 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nacos_client
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/utils"
"log"
"os"
"strconv"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 16:39
**/
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
)
type NacosClient struct {
clientConfigValid bool
@ -27,45 +35,43 @@ type NacosClient struct {
serverConfigs []constant.ServerConfig
}
// 设置 clientConfig
// SetClientConfig is use to set nacos client Config
func (client *NacosClient) SetClientConfig(config constant.ClientConfig) (err error) {
if config.TimeoutMs <= 0 {
err = errors.New("[client.SetClientConfig] config.TimeoutMs should > 0")
return
}
if config.TimeoutMs <= config.ListenInterval {
err = errors.New("[client.SetClientConfig] config.TimeoutMs should > config.ListenInterval")
return
config.TimeoutMs = 10 * 1000
}
if config.BeatInterval <= 0 {
config.BeatInterval = 5 * 1000
}
if config.ListenInterval < 10*1000 {
config.ListenInterval = 10 * 1000
}
if config.UpdateThreadNum <= 0 {
config.UpdateThreadNum = 20
}
if len(config.LogLevel) == 0 {
config.LogLevel = "info"
}
if config.CacheDir == "" {
config.CacheDir = utils.GetCurrentPath() + string(os.PathSeparator) + "cache"
config.CacheDir = file.GetCurrentPath() + string(os.PathSeparator) + "cache"
}
if config.LogDir == "" {
config.LogDir = utils.GetCurrentPath() + string(os.PathSeparator) + "log"
config.LogDir = file.GetCurrentPath() + string(os.PathSeparator) + "log"
}
log.Printf("[INFO] logDir:<%s> cacheDir:<%s>", config.LogDir, config.CacheDir)
client.clientConfig = config
client.clientConfigValid = true
return
}
// 设置 serverConfigs
// SetServerConfig is use to set nacos server config
func (client *NacosClient) SetServerConfig(configs []constant.ServerConfig) (err error) {
if len(configs) <= 0 {
err = errors.New("[client.SetServerConfig] configs can not be empty")
//it's may be use endpoint to get nacos server address
client.serverConfigsValid = true
return
}
@ -77,13 +83,16 @@ func (client *NacosClient) SetServerConfig(configs []constant.ServerConfig) (err
if len(configs[i].ContextPath) <= 0 {
configs[i].ContextPath = constant.DEFAULT_CONTEXT_PATH
}
if len(configs[i].Scheme) <= 0 {
configs[i].Scheme = constant.DEFAULT_SERVER_SCHEME
}
}
client.serverConfigs = configs
client.serverConfigsValid = true
return
}
// 获取 clientConfig
// GetClientConfig use to get client config
func (client *NacosClient) GetClientConfig() (config constant.ClientConfig, err error) {
config = client.clientConfig
if !client.clientConfigValid {
@ -92,7 +101,7 @@ func (client *NacosClient) GetClientConfig() (config constant.ClientConfig, err
return
}
// 获取serverConfigs
// GetServerConfig use to get server config
func (client *NacosClient) GetServerConfig() (configs []constant.ServerConfig, err error) {
configs = client.serverConfigs
if !client.serverConfigsValid {
@ -101,6 +110,7 @@ func (client *NacosClient) GetServerConfig() (configs []constant.ServerConfig, e
return
}
// SetHttpAgent use to set http agent
func (client *NacosClient) SetHttpAgent(agent http_agent.IHttpAgent) (err error) {
if agent == nil {
err = errors.New("[client.SetHttpAgent] http agent can not be nil")
@ -110,6 +120,7 @@ func (client *NacosClient) SetHttpAgent(agent http_agent.IHttpAgent) (err error)
return
}
// GetHttpAgent use to get http agent
func (client *NacosClient) GetHttpAgent() (agent http_agent.IHttpAgent, err error) {
if client.agent == nil {
err = errors.New("[client.GetHttpAgent] invalid http agent")

View File

@ -1,26 +1,40 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nacos_client
import (
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 16:32
**/
//go:generate mockgen -destination mock_nacos_client_interface.go -package nacos_client -source=./nacos_client_interface.go
type INacosClient interface {
//SetClientConfig is use to set nacos client config
SetClientConfig(constant.ClientConfig) error
//SetServerConfig is use to set nacos server config
SetServerConfig([]constant.ServerConfig) error
//GetClientConfig use to get client config
GetClientConfig() (constant.ClientConfig, error)
//GetServerConfig use to get server config
GetServerConfig() ([]constant.ServerConfig, error)
//SetHttpAgent use to set http agent
SetHttpAgent(http_agent.IHttpAgent) error
//GetHttpAgent use to get http agent
GetHttpAgent() (http_agent.IHttpAgent, error)
}

View File

@ -1,91 +0,0 @@
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
nsema "github.com/toolkits/concurrent/semaphore"
"log"
"strconv"
"time"
)
type BeatReactor struct {
beatMap cache.ConcurrentMap
serviceProxy NamingProxy
clientBeatInterval int64
beatThreadCount int
beatRecordMap cache.ConcurrentMap
}
const Default_Bead_Thread_Num = 20
func NewBeatReactor(serviceProxy NamingProxy, clientBeatInterval int64) BeatReactor {
br := BeatReactor{}
if clientBeatInterval <= 0 {
clientBeatInterval = 5 * 1000
}
br.beatMap = cache.NewConcurrentMap()
br.serviceProxy = serviceProxy
br.clientBeatInterval = clientBeatInterval
br.beatThreadCount = Default_Bead_Thread_Num
br.beatRecordMap = cache.NewConcurrentMap()
go br.sendBeat()
return br
}
func buildKey(serviceName string, ip string, port uint64) string {
return serviceName + constant.NAMING_INSTANCE_ID_SPLITTER + ip + constant.NAMING_INSTANCE_ID_SPLITTER + strconv.Itoa(int(port))
}
func (br *BeatReactor) AddBeatInfo(serviceName string, beatInfo model.BeatInfo) {
log.Printf("[INFO] adding beat: <%s> to beat map.\n", utils.ToJsonString(beatInfo))
br.beatMap.Set(buildKey(serviceName, beatInfo.Ip, beatInfo.Port), beatInfo)
}
func (br *BeatReactor) RemoveBeatInfo(serviceName string, ip string, port uint64) {
log.Printf("[INFO] remove beat: %s@%s:%d from beat map.\n", serviceName, ip, port)
br.beatMap.Remove(buildKey(serviceName, ip, port))
}
func (br *BeatReactor) sendBeat() {
sema := nsema.NewSemaphore(br.beatThreadCount)
for {
if br.beatMap.Count() > 0 {
for k, item := range br.beatMap.Items() {
var lastBeatTime int64
beatInfo := item.(model.BeatInfo)
store, ok := br.beatRecordMap.Get(k)
if !ok {
lastBeatTime = 0
} else {
lastBeatTime = store.(int64)
}
if utils.CurrentMillis()-lastBeatTime > br.clientBeatInterval && !beatInfo.Scheduled {
sema.Acquire()
beatInfo.Scheduled = true
br.beatMap.Set(k, beatInfo)
go func(k string, beatInfo model.BeatInfo) {
defer sema.Release()
beatInterval, err := br.serviceProxy.SendBeat(beatInfo)
if err != nil {
log.Printf("[ERROR]:beat to server return error:%s \n", err.Error())
beatInfo.Scheduled = false
br.beatMap.Set(k, beatInfo)
return
}
if beatInterval > 0 {
br.clientBeatInterval = beatInterval
}
beatInfo.Scheduled = false
br.beatMap.Set(k, beatInfo)
br.beatRecordMap.Set(k, utils.CurrentMillis())
}(k, beatInfo)
}
}
}
time.Sleep(200 * time.Millisecond)
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,191 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_cache
import (
"os"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type ServiceInfoHolder struct {
ServiceInfoMap sync.Map
updateCacheWhenEmpty bool
cacheDir string
notLoadCacheAtStart bool
subCallback *SubscribeCallback
UpdateTimeMap sync.Map
}
func NewServiceInfoHolder(namespace, cacheDir string, updateCacheWhenEmpty, notLoadCacheAtStart bool) *ServiceInfoHolder {
cacheDir = cacheDir + string(os.PathSeparator) + "naming" + string(os.PathSeparator) + namespace
serviceInfoHolder := &ServiceInfoHolder{
updateCacheWhenEmpty: updateCacheWhenEmpty,
notLoadCacheAtStart: notLoadCacheAtStart,
cacheDir: cacheDir,
subCallback: NewSubscribeCallback(),
UpdateTimeMap: sync.Map{},
ServiceInfoMap: sync.Map{},
}
if !notLoadCacheAtStart {
serviceInfoHolder.loadCacheFromDisk()
}
return serviceInfoHolder
}
func (s *ServiceInfoHolder) loadCacheFromDisk() {
serviceMap := cache.ReadServicesFromFile(s.cacheDir)
if serviceMap == nil || len(serviceMap) == 0 {
return
}
for k, v := range serviceMap {
s.ServiceInfoMap.Store(k, v)
}
}
func (s *ServiceInfoHolder) ProcessServiceJson(data string) {
s.ProcessService(util.JsonToService(data))
}
func (s *ServiceInfoHolder) ProcessService(service *model.Service) {
if service == nil {
return
}
if !s.updateCacheWhenEmpty {
//if instance list is empty,not to update cache
if service.Hosts == nil || len(service.Hosts) == 0 {
logger.Warnf("instance list is empty, updateCacheWhenEmpty is set to false, callback is not triggered. service name:%s", service.Name)
return
}
}
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
oldDomain, ok := s.ServiceInfoMap.Load(cacheKey)
if ok && oldDomain.(model.Service).LastRefTime >= service.LastRefTime {
logger.Warnf("out of date data received, old-t: %d, new-t: %d", oldDomain.(model.Service).LastRefTime, service.LastRefTime)
return
}
s.UpdateTimeMap.Store(cacheKey, uint64(util.CurrentMillis()))
s.ServiceInfoMap.Store(cacheKey, *service)
if !ok || checkInstanceChanged(oldDomain, *service) {
logger.Infof("service key:%s was updated to:%s", cacheKey, util.ToJsonString(service))
cache.WriteServicesToFile(service, cacheKey, s.cacheDir)
s.subCallback.ServiceChanged(cacheKey, service)
}
var count int
s.ServiceInfoMap.Range(func(key, value interface{}) bool {
count++
return true
})
monitor.GetServiceInfoMapSizeMonitor().Set(float64(count))
}
func (s *ServiceInfoHolder) GetServiceInfo(serviceName, groupName, clusters string) (model.Service, bool) {
cacheKey := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
//todo FailoverReactor
service, ok := s.ServiceInfoMap.Load(cacheKey)
if ok {
return service.(model.Service), ok
}
return model.Service{}, ok
}
func (s *ServiceInfoHolder) RegisterCallback(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
s.subCallback.AddCallbackFunc(serviceName, clusters, callbackWrapper)
}
func (s *ServiceInfoHolder) DeregisterCallback(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
s.subCallback.RemoveCallbackFunc(serviceName, clusters, callbackWrapper)
}
func (s *ServiceInfoHolder) StopUpdateIfContain(serviceName, clusters string) {
cacheKey := util.GetServiceCacheKey(serviceName, clusters)
s.ServiceInfoMap.Delete(cacheKey)
}
func (s *ServiceInfoHolder) IsSubscribed(serviceName, clusters string) bool {
return s.subCallback.IsSubscribed(serviceName, clusters)
}
func checkInstanceChanged(oldDomain interface{}, service model.Service) bool {
if oldDomain == nil {
return true
}
oldService := oldDomain.(model.Service)
return isServiceInstanceChanged(oldService, service)
}
// return true when service instance changed ,otherwise return false.
func isServiceInstanceChanged(oldService, newService model.Service) bool {
oldHostsLen := len(oldService.Hosts)
newHostsLen := len(newService.Hosts)
if oldHostsLen != newHostsLen {
return true
}
// compare refTime
oldRefTime := oldService.LastRefTime
newRefTime := newService.LastRefTime
if oldRefTime > newRefTime {
logger.Warnf("out of date data received, old-t: %v , new-t: %v", oldRefTime, newRefTime)
return false
}
// sort instance list
oldInstance := oldService.Hosts
newInstance := make([]model.Instance, len(newService.Hosts))
copy(newInstance, newService.Hosts)
sortInstance(oldInstance)
sortInstance(newInstance)
return !reflect.DeepEqual(oldInstance, newInstance)
}
type instanceSorter []model.Instance
func (s instanceSorter) Len() int {
return len(s)
}
func (s instanceSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s instanceSorter) Less(i, j int) bool {
insI, insJ := s[i], s[j]
// using ip and port to sort
ipNum1, _ := strconv.Atoi(strings.ReplaceAll(insI.Ip, ".", ""))
ipNum2, _ := strconv.Atoi(strings.ReplaceAll(insJ.Ip, ".", ""))
if ipNum1 < ipNum2 {
return true
}
if insI.Port < insJ.Port {
return true
}
return false
}
// sort instances
func sortInstance(instances []model.Instance) {
sort.Sort(instanceSorter(instances))
}

View File

@ -0,0 +1,152 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_cache
import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/stretchr/testify/assert"
)
func TestServiceInfoHolder_isServiceInstanceChanged(t *testing.T) {
rand.Seed(time.Now().Unix())
defaultIp := createRandomIp()
defaultPort := creatRandomPort()
serviceA := model.Service{
LastRefTime: 1000,
Hosts: []model.Instance{
{
Ip: defaultIp,
Port: defaultPort,
},
{
Ip: defaultIp,
Port: defaultPort + 1,
},
{
Ip: defaultIp,
Port: defaultPort + 2,
},
},
}
serviceB := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: defaultIp,
Port: defaultPort,
},
{
Ip: defaultIp,
Port: defaultPort + 3,
},
{
Ip: defaultIp,
Port: defaultPort + 4,
},
},
}
ip := createRandomIp()
serviceC := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: ip,
Port: defaultPort,
},
{
Ip: ip,
Port: defaultPort + 3,
},
{
Ip: ip,
Port: defaultPort + 4,
},
},
}
t.Run("compareWithSelf", func(t *testing.T) {
changed := isServiceInstanceChanged(serviceA, serviceA)
assert.Equal(t, false, changed)
})
// compareWithIp
t.Run("compareWithIp", func(t *testing.T) {
changed := isServiceInstanceChanged(serviceA, serviceC)
assert.Equal(t, true, changed)
})
// compareWithPort
t.Run("compareWithPort", func(t *testing.T) {
changed := isServiceInstanceChanged(serviceA, serviceB)
assert.Equal(t, true, changed)
})
}
func TestHostReactor_isServiceInstanceChangedWithUnOrdered(t *testing.T) {
rand.Seed(time.Now().Unix())
serviceA := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
},
}
serviceB := model.Service{
LastRefTime: 1001,
Hosts: []model.Instance{
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
{
Ip: createRandomIp(),
Port: creatRandomPort(),
},
},
}
logger.Info("serviceA:%s and serviceB:%s are comparing", serviceA.Hosts, serviceB.Hosts)
changed := isServiceInstanceChanged(serviceA, serviceB)
assert.True(t, changed)
}
// create random ip addr
func createRandomIp() string {
ip := fmt.Sprintf("%d.%d.%d.%d", rand.Intn(255), rand.Intn(255), rand.Intn(255), rand.Intn(255))
return ip
}
func creatRandomPort() uint64 {
return rand.Uint64()
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_cache
import (
"sync"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type SubscribeCallback struct {
callbackFuncMap cache.ConcurrentMap
mux *sync.Mutex
}
func NewSubscribeCallback() *SubscribeCallback {
return &SubscribeCallback{callbackFuncMap: cache.NewConcurrentMap(), mux: new(sync.Mutex)}
}
func (ed *SubscribeCallback) IsSubscribed(serviceName, clusters string) bool {
key := util.GetServiceCacheKey(serviceName, clusters)
funcs, ok := ed.callbackFuncMap.Get(key)
if ok {
return len(funcs.([]*SubscribeCallbackFuncWrapper)) > 0
}
return false
}
func (ed *SubscribeCallback) AddCallbackFunc(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
key := util.GetServiceCacheKey(serviceName, clusters)
ed.mux.Lock()
defer ed.mux.Unlock()
var funcSlice []*SubscribeCallbackFuncWrapper
old, ok := ed.callbackFuncMap.Get(key)
if ok {
funcSlice = append(funcSlice, old.([]*SubscribeCallbackFuncWrapper)...)
}
funcSlice = append(funcSlice, callbackWrapper)
ed.callbackFuncMap.Set(key, funcSlice)
}
func (ed *SubscribeCallback) RemoveCallbackFunc(serviceName string, clusters string, callbackWrapper *SubscribeCallbackFuncWrapper) {
logger.Info("removing " + serviceName + " with " + clusters + " to listener map")
key := util.GetServiceCacheKey(serviceName, clusters)
funcs, ok := ed.callbackFuncMap.Get(key)
if ok && funcs != nil {
var newFuncs []*SubscribeCallbackFuncWrapper
for _, funcItem := range funcs.([]*SubscribeCallbackFuncWrapper) {
if funcItem.CallbackFunc != callbackWrapper.CallbackFunc || !funcItem.Selector.Equals(callbackWrapper.Selector) {
newFuncs = append(newFuncs, funcItem)
}
}
ed.callbackFuncMap.Set(key, newFuncs)
}
}
func (ed *SubscribeCallback) ServiceChanged(cacheKey string, service *model.Service) {
funcs, ok := ed.callbackFuncMap.Get(cacheKey)
if ok {
for _, funcItem := range funcs.([]*SubscribeCallbackFuncWrapper) {
funcItem.notifyListener(service)
}
}
}

View File

@ -0,0 +1,230 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_cache
import (
"fmt"
"log"
"strings"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
func TestEventDispatcher_AddCallbackFuncs(t *testing.T) {
service := model.Service{
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Println(util.ToJsonString(ed.callbackFuncMap))
},
}
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*SubscribeCallbackFuncWrapper)
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0].CallbackFunc, &param.SubscribeCallback, "callback function must be equal!")
}
}
func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
service := model.Service{
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Printf("func1:%s \n", util.ToJsonString(services))
},
}
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
assert.Equal(t, len(ed.callbackFuncMap.Items()), 1, "callback funcs map length should be 1")
param2 := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Printf("func2:%s \n", util.ToJsonString(services))
},
}
clusterSelector2 := NewClusterSelector(param2.Clusters)
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &param2.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
assert.Equal(t, len(ed.callbackFuncMap.Items()), 1, "callback funcs map length should be 2")
for k, v := range ed.callbackFuncMap.Items() {
log.Printf("key:%s,%d", k, len(v.([]*SubscribeCallbackFuncWrapper)))
}
ed.RemoveCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*SubscribeCallbackFuncWrapper)
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0].CallbackFunc, &param.SubscribeCallback, "callback function must be equal!")
}
}
func TestSubscribeCallback_ServiceChanged(t *testing.T) {
service := model.Service{
Name: "public@@Test",
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
LastRefTime: uint64(time.Now().Unix()),
}
var hosts []model.Instance
host := model.Instance{
Enable: true,
InstanceId: "123",
Port: 8080,
Ip: "127.0.0.1",
Weight: 10,
ServiceName: "public@@Test",
ClusterName: strings.Join([]string{"default"}, ","),
}
hosts = append(hosts, host)
service.Hosts = hosts
ed := NewSubscribeCallback()
param := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("func1:%s \n", util.ToJsonString(services))
},
}
clusterSelector := NewClusterSelector(param.Clusters)
callbackWrapper := NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), callbackWrapper)
param2 := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("func2:%s \n", util.ToJsonString(services))
},
}
clusterSelector2 := NewClusterSelector(param2.Clusters)
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &param2.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), callbackWrapper2)
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
ed.ServiceChanged(cacheKey, &service)
}
func TestSubscribeCallback_RemoveCallbackFunc(t *testing.T) {
ed := NewSubscribeCallback()
serviceName := "Test"
clusters := "default"
groupName := "public"
callback1 := func(services []model.Instance, err error) {
log.Printf("callback1:%s \n", util.ToJsonString(services))
}
clusterSelector1 := NewClusterSelector([]string{clusters})
callbackWrapper1 := NewSubscribeCallbackFuncWrapper(clusterSelector1, &callback1)
callback2 := func(services []model.Instance, err error) {
log.Printf("callback2:%s \n", util.ToJsonString(services))
}
clusterSelector2 := NewClusterSelector([]string{clusters})
callbackWrapper2 := NewSubscribeCallbackFuncWrapper(clusterSelector2, &callback2)
// Add both callbacks
ed.AddCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper1)
ed.AddCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper2)
assert.True(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
// Remove the first callback
ed.RemoveCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper1)
// Check if only the second callback remains
cacheKey := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
funcs, ok := ed.callbackFuncMap.Get(cacheKey)
if !ok || len(funcs.([]*SubscribeCallbackFuncWrapper)) != 1 {
t.Errorf("Expected 1 callback function, got %d", len(funcs.([]*SubscribeCallbackFuncWrapper)))
}
assert.True(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
// Remove the second callback
ed.RemoveCallbackFunc(util.GetGroupName(serviceName, groupName), clusters, callbackWrapper2)
// Check if no callbacks remain
funcs, ok = ed.callbackFuncMap.Get(cacheKey)
if ok && len(funcs.([]*SubscribeCallbackFuncWrapper)) != 0 {
t.Errorf("Expected 0 callback functions, got %d", len(funcs.([]*func(services []model.Instance, err error))))
}
assert.False(t, ed.IsSubscribed(util.GetGroupName(serviceName, groupName), clusters))
}

View File

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

View File

@ -1,66 +1,117 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/pkg/errors"
"context"
"math"
"math/rand"
"os"
"strings"
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// NamingClient ...
type NamingClient struct {
nacos_client.INacosClient
hostReactor HostReactor
serviceProxy NamingProxy
subCallback SubscribeCallback
beatReactor BeatReactor
indexMap cache.ConcurrentMap
ctx context.Context
cancel context.CancelFunc
serviceProxy naming_proxy.INamingProxy
serviceInfoHolder *naming_cache.ServiceInfoHolder
isClosed bool
mutex sync.Mutex
}
func NewNamingClient(nc nacos_client.INacosClient) (NamingClient, error) {
naming := NamingClient{}
clientConfig, err :=
nc.GetClientConfig()
// NewNamingClient ...
func NewNamingClient(nc nacos_client.INacosClient) (*NamingClient, error) {
return NewNamingClientWithRamCredentialProvider(nc, nil)
}
// NewNamingClientWithRamCredentialProvider ...
func NewNamingClientWithRamCredentialProvider(nc nacos_client.INacosClient, provider security.RamCredentialProvider) (*NamingClient, error) {
ctx, cancel := context.WithCancel(context.Background())
rand.Seed(time.Now().UnixNano())
naming := &NamingClient{INacosClient: nc, ctx: ctx, cancel: cancel}
clientConfig, err := nc.GetClientConfig()
if err != nil {
return naming, err
}
serverConfig, err := nc.GetServerConfig()
if err != nil {
return naming, err
}
httpAgent, err := nc.GetHttpAgent()
if err != nil {
return naming, err
}
err = logger.InitLog(clientConfig.LogDir)
if err = initLogger(clientConfig); err != nil {
return naming, err
}
if clientConfig.NamespaceId == "" {
clientConfig.NamespaceId = constant.DEFAULT_NAMESPACE_ID
}
naming.serviceInfoHolder = naming_cache.NewServiceInfoHolder(clientConfig.NamespaceId, clientConfig.CacheDir,
clientConfig.UpdateCacheWhenEmpty, clientConfig.NotLoadCacheAtStart)
naming.serviceProxy, err = NewNamingProxyDelegateWithRamCredentialProvider(ctx, clientConfig, serverConfig, httpAgent, naming.serviceInfoHolder, provider)
if clientConfig.AsyncUpdateService {
go NewServiceInfoUpdater(ctx, naming.serviceInfoHolder, clientConfig.UpdateThreadNum, naming.serviceProxy).asyncUpdateService()
}
if err != nil {
return naming, err
}
naming.subCallback = NewSubscribeCallback()
naming.serviceProxy, err = NewNamingProxy(clientConfig, serverConfig, httpAgent)
if err != nil {
return naming, err
}
naming.hostReactor = NewHostReactor(naming.serviceProxy, clientConfig.CacheDir+string(os.PathSeparator)+"naming",
clientConfig.UpdateThreadNum, clientConfig.NotLoadCacheAtStart, naming.subCallback, clientConfig.UpdateCacheWhenEmpty)
naming.beatReactor = NewBeatReactor(naming.serviceProxy, clientConfig.BeatInterval)
naming.indexMap = cache.NewConcurrentMap()
return naming, nil
}
// 注册服务实例
func initLogger(clientConfig constant.ClientConfig) error {
return logger.InitLogger(logger.BuildLoggerConfig(clientConfig))
}
// RegisterInstance ...
func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool, error) {
if param.GroupName == "" {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
}
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
if param.Metadata == nil {
param.Metadata = make(map[string]string)
}
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
@ -71,63 +122,157 @@ func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
}
beatInfo := model.BeatInfo{
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
}
func (sc *NamingClient) BatchRegisterInstance(param vo.BatchRegisterInstanceParam) (bool, error) {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
}
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
if len(param.Instances) == 0 {
return false, errors.New("instances cannot be empty!")
}
var modelInstances []model.Instance
for _, param := range param.Instances {
if !param.Ephemeral {
return false, errors.Errorf("Batch registration does not allow persistent instance registration! instance:%+v", param)
}
modelInstances = append(modelInstances, model.Instance{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ClusterName: param.ClusterName,
Healthy: param.Healthy,
Enable: param.Enable,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
})
}
return sc.serviceProxy.BatchRegisterInstance(param.ServiceName, param.GroupName, modelInstances)
}
// DeregisterInstance ...
func (sc *NamingClient) DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
ClusterName: param.Cluster,
Ephemeral: param.Ephemeral,
}
return sc.serviceProxy.DeregisterInstance(param.ServiceName, param.GroupName, instance)
}
// UpdateInstance ...
func (sc *NamingClient) UpdateInstance(param vo.UpdateInstanceParam) (bool, error) {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
}
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
if param.Metadata == nil {
param.Metadata = make(map[string]string)
}
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ServiceName: utils.GetGroupName(param.ServiceName, param.GroupName),
Cluster: param.ClusterName,
ClusterName: param.ClusterName,
Healthy: param.Healthy,
Enable: param.Enable,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
}
_, err := sc.serviceProxy.RegisterInstance(utils.GetGroupName(param.ServiceName, param.GroupName), param.GroupName, instance)
if err != nil {
return false, err
}
if instance.Ephemeral {
sc.beatReactor.AddBeatInfo(utils.GetGroupName(param.ServiceName, param.GroupName), beatInfo)
}
return true, nil
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
}
// 注销服务实例
func (sc *NamingClient) DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error) {
if param.GroupName == "" {
// GetService Get service info by Group and DataId, clusters was optional
func (sc *NamingClient) GetService(param vo.GetServiceParam) (service model.Service, err error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
_, err := sc.serviceProxy.DeregisterInstance(utils.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port, param.Cluster, param.Ephemeral)
if err != nil {
return false, err
var ok bool
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
}
sc.beatReactor.RemoveBeatInfo(utils.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port)
return true, nil
service.Clusters = clusters
service.Hosts = clusterSelector.SelectInstance(&service)
return service, err
}
// 获取服务列表
func (sc *NamingClient) GetService(param vo.GetServiceParam) (model.Service, error) {
if param.GroupName == "" {
// GetAllServicesInfo Get all instance by Namespace and Group with page
func (sc *NamingClient) GetAllServicesInfo(param vo.GetAllServiceInfoParam) (model.ServiceList, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
return service, nil
clientConfig, _ := sc.GetClientConfig()
if len(param.NameSpace) == 0 {
if len(clientConfig.NamespaceId) == 0 {
param.NameSpace = constant.DEFAULT_NAMESPACE_ID
} else {
param.NameSpace = clientConfig.NamespaceId
}
}
services, err := sc.serviceProxy.GetServiceList(param.PageNo, param.PageSize, param.GroupName, param.NameSpace, &model.ExpressionSelector{})
return services, err
}
// SelectAllInstances Get all instance by DataId 和 Group
func (sc *NamingClient) SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error) {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
if service.Hosts == nil || len(service.Hosts) == 0 {
return []model.Instance{}, errors.New("instance list is empty!")
var (
service model.Service
ok bool
err error
)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
}
return service.Hosts, nil
if err != nil {
return []model.Instance{}, err
}
instances := clusterSelector.SelectInstance(&service)
if instances == nil || len(instances) == 0 {
return []model.Instance{}, err
}
return instances, err
}
// SelectInstances Get all instance by DataId, Group and Health
func (sc *NamingClient) SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error) {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
var (
service model.Service
ok bool
err error
)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
if err != nil {
return nil, err
}
}
service.Hosts = clusterSelector.SelectInstance(&service)
return sc.selectInstances(service, param.HealthyOnly)
}
@ -137,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)
@ -145,11 +291,25 @@ func (sc *NamingClient) selectInstances(service model.Service, healthy bool) ([]
return result, nil
}
// SelectOneHealthyInstance Get one healthy instance by DataId and Group
func (sc *NamingClient) SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error) {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service := sc.hostReactor.GetServiceInfo(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
var (
service model.Service
ok bool
err error
)
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, "")
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
if err != nil {
return nil, err
}
}
service.Hosts = clusterSelector.SelectInstance(&service)
return sc.selectOneHealthyInstances(service)
}
@ -173,62 +333,49 @@ func (sc *NamingClient) selectOneHealthyInstances(service model.Service) (*model
return nil, errors.New("healthy instance list is empty!")
}
randomInstances := random(result, mw)
key := utils.GetServiceCacheKey(service.Name, service.Clusters)
i, indexOk := sc.indexMap.Get(key)
var index int
if !indexOk {
index = rand.Intn(len(randomInstances))
} else {
index = i.(int)
index += 1
if index >= len(randomInstances) {
index = index % len(randomInstances)
}
}
sc.indexMap.Set(key, index)
return &randomInstances[index], nil
instance := newChooser(result).pick()
return &instance, nil
}
func random(instances []model.Instance, mw int) []model.Instance {
if len(instances) <= 1 || mw <= 1 {
return instances
}
//实例交叉插入列表,避免列表中是连续的实例
var result = make([]model.Instance, 0)
for i := 1; i <= mw; i++ {
for _, host := range instances {
if int(math.Ceil(host.Weight)) >= i {
result = append(result, host)
}
}
}
return result
}
// 服务监听
// Subscribe ...
func (sc *NamingClient) Subscribe(param *vo.SubscribeParam) error {
if param.GroupName == "" {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
serviceParam := vo.GetServiceParam{
ServiceName: param.ServiceName,
GroupName: param.GroupName,
Clusters: param.Clusters,
}
sc.subCallback.AddCallbackFuncs(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
_, err := sc.GetService(serviceParam)
if err != nil {
return err
}
return nil
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
callbackWrapper := naming_cache.NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
sc.serviceInfoHolder.RegisterCallback(util.GetGroupName(param.ServiceName, param.GroupName), "", callbackWrapper)
_, err := sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, "")
return err
}
//取消服务监听
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) error {
sc.subCallback.RemoveCallbackFuncs(utils.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
return nil
// Unsubscribe ...
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) (err error) {
clusterSelector := naming_cache.NewClusterSelector(param.Clusters)
callbackWrapper := naming_cache.NewSubscribeCallbackFuncWrapper(clusterSelector, &param.SubscribeCallback)
serviceFullName := util.GetGroupName(param.ServiceName, param.GroupName)
sc.serviceInfoHolder.DeregisterCallback(serviceFullName, "", callbackWrapper)
if !sc.serviceInfoHolder.IsSubscribed(serviceFullName, "") {
err = sc.serviceProxy.Unsubscribe(param.ServiceName, param.GroupName, "")
}
return err
}
// ServerHealthy ...
func (sc *NamingClient) ServerHealthy() bool {
return sc.serviceProxy.ServerHealthy()
}
// CloseClient ...
func (sc *NamingClient) CloseClient() {
sc.mutex.Lock()
defer sc.mutex.Unlock()
if sc.isClosed {
return
}
sc.serviceProxy.CloseClient()
sc.cancel()
sc.isClosed = true
}

View File

@ -1,36 +1,120 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 09:56
**/
//go:generate mockgen -destination ../../mock/mock_service_client_interface.go -package mock -source=./service_client_interface.go
// INamingClient interface for naming client
type INamingClient interface {
// 注册服务实例
// RegisterInstance use to register instance
// Ip require
// Port require
// Weight require,it must be lager than 0
// Enable require,the instance can be access or not
// Healthy require,the instance is health or not
// Metadata optional
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
RegisterInstance(param vo.RegisterInstanceParam) (bool, error)
// 注销服务实例
// BatchRegisterInstance use to batch register instance
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Instances require,batch register instance list (serviceName, groupName in instances do not need to be set)
BatchRegisterInstance(param vo.BatchRegisterInstanceParam) (bool, error)
// DeregisterInstance use to deregister instance
// Ip required
// Port required
// Tenant optional
// Cluster optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error)
// 获取服务信息
// UpdateInstance use to update instance
// Ip require
// Port require
// Weight require,it must be lager than 0
// Enable require,the instance can be access or not
// Healthy require,the instance is health or not
// Metadata optional
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
UpdateInstance(param vo.UpdateInstanceParam) (bool, error)
// GetService use to get service
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
GetService(param vo.GetServiceParam) (model.Service, error)
//获取所有的实例列表
// SelectAllInstances return all instances,include healthy=false,enable=false,weight<=0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error)
// 获取实例列表
// SelectInstances only return the instances of healthy=${HealthyOnly},enable=true and weight>0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// HealthyOnly optional
SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error)
//获取一个健康的实例
// SelectOneHealthyInstance return one instance by WRR strategy for load balance
// And the instance should be health=true,enable=true and weight>0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error)
// 服务监听
// Subscribe use to subscribe service change event
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// SubscribeCallback require
Subscribe(param *vo.SubscribeParam) error
//取消监听
// Unsubscribe use to unsubscribe service change event
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// SubscribeCallback require
Unsubscribe(param *vo.SubscribeParam) error
// GetAllServicesInfo use to get all service info by page
GetAllServicesInfo(param vo.GetAllServiceInfoParam) (model.ServiceList, error)
// ServerHealthy use to check the connectivity to server
ServerHealthy() bool
//CloseClient close the GRPC client
CloseClient()
}

View File

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

View File

@ -0,0 +1,130 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_grpc
import (
"strings"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type ConnectionEventListener struct {
clientProxy naming_proxy.INamingProxy
registeredInstanceCached cache.ConcurrentMap
subscribes cache.ConcurrentMap
}
func NewConnectionEventListener(clientProxy naming_proxy.INamingProxy) *ConnectionEventListener {
return &ConnectionEventListener{
clientProxy: clientProxy,
registeredInstanceCached: cache.NewConcurrentMap(),
subscribes: cache.NewConcurrentMap(),
}
}
func (c *ConnectionEventListener) OnConnected() {
c.redoSubscribe()
c.redoRegisterEachService()
}
func (c *ConnectionEventListener) OnDisConnect() {
}
func (c *ConnectionEventListener) redoSubscribe() {
grpcProxy, ok := c.clientProxy.(*NamingGrpcProxy)
if !ok {
logger.Error("redo subscribe clientProxy type error")
return
}
for _, key := range c.subscribes.Keys() {
info := strings.Split(key, constant.SERVICE_INFO_SPLITER)
var err error
var service model.Service
if len(info) > 2 {
service, err = c.clientProxy.Subscribe(info[1], info[0], info[2])
} else {
service, err = c.clientProxy.Subscribe(info[1], info[0], "")
}
if err != nil {
logger.Warnf("redo subscribe service:%s faild:%+v", info[1], err)
continue
}
grpcProxy.serviceInfoHolder.ProcessService(&service)
}
}
func (c *ConnectionEventListener) redoRegisterEachService() {
for k, v := range c.registeredInstanceCached.Items() {
info := strings.Split(k, constant.SERVICE_INFO_SPLITER)
serviceName := info[1]
groupName := info[0]
if instance, ok := v.(model.Instance); ok {
if _, err := c.clientProxy.RegisterInstance(serviceName, groupName, instance); err != nil {
logger.Warnf("redo register service:%s groupName:%s faild:%s", info[1], info[0], err.Error())
continue
}
}
if instances, ok := v.([]model.Instance); ok {
if _, err := c.clientProxy.BatchRegisterInstance(serviceName, groupName, instances); err != nil {
logger.Warnf("redo batch register service:%s groupName:%s faild:%s", info[1], info[0], err.Error())
continue
}
}
}
}
func (c *ConnectionEventListener) CacheInstanceForRedo(serviceName, groupName string, instance model.Instance) {
key := util.GetGroupName(serviceName, groupName)
c.registeredInstanceCached.Set(key, instance)
}
func (c *ConnectionEventListener) CacheInstancesForRedo(serviceName, groupName string, instances []model.Instance) {
key := util.GetGroupName(serviceName, groupName)
c.registeredInstanceCached.Set(key, instances)
}
func (c *ConnectionEventListener) RemoveInstanceForRedo(serviceName, groupName string, instance model.Instance) {
key := util.GetGroupName(serviceName, groupName)
_, ok := c.registeredInstanceCached.Get(key)
if !ok {
return
}
c.registeredInstanceCached.Remove(key)
}
func (c *ConnectionEventListener) CacheSubscriberForRedo(fullServiceName, clusters string) {
key := util.GetServiceCacheKey(fullServiceName, clusters)
if !c.IsSubscriberCached(key) {
c.subscribes.Set(key, struct{}{})
}
}
func (c *ConnectionEventListener) IsSubscriberCached(key string) bool {
_, ok := c.subscribes.Get(key)
return ok
}
func (c *ConnectionEventListener) RemoveSubscriberForRedo(fullServiceName, clusters string) {
c.subscribes.Remove(util.GetServiceCacheKey(fullServiceName, clusters))
}

View File

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

View File

@ -0,0 +1,204 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_grpc
import (
"context"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
// NamingGrpcProxy ...
type NamingGrpcProxy struct {
clientConfig constant.ClientConfig
nacosServer *nacos_server.NacosServer
rpcClient rpc.IRpcClient
eventListener *ConnectionEventListener
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
// NewNamingGrpcProxy create naming grpc proxy
func NewNamingGrpcProxy(ctx context.Context, clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer,
serviceInfoHolder *naming_cache.ServiceInfoHolder) (*NamingGrpcProxy, error) {
srvProxy := NamingGrpcProxy{
clientConfig: clientCfg,
nacosServer: nacosServer,
serviceInfoHolder: serviceInfoHolder,
}
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
labels := map[string]string{
constant.LABEL_SOURCE: constant.LABEL_SOURCE_SDK,
constant.LABEL_MODULE: constant.LABEL_MODULE_NAMING,
}
iRpcClient, err := rpc.CreateClient(ctx, uid.String(), rpc.GRPC, labels, srvProxy.nacosServer, &clientCfg.TLSCfg, clientCfg.AppConnLabels)
if err != nil {
return nil, err
}
srvProxy.rpcClient = iRpcClient
rpcClient := srvProxy.rpcClient.GetRpcClient()
rpcClient.Start()
rpcClient.RegisterServerRequestHandler(func() rpc_request.IRequest {
return &rpc_request.NotifySubscriberRequest{NamingRequest: &rpc_request.NamingRequest{}}
}, &rpc.NamingPushRequestHandler{ServiceInfoHolder: serviceInfoHolder})
srvProxy.eventListener = NewConnectionEventListener(&srvProxy)
rpcClient.RegisterConnectionListener(srvProxy.eventListener)
return &srvProxy, nil
}
func (proxy *NamingGrpcProxy) requestToServer(request rpc_request.IRequest) (rpc_response.IResponse, error) {
start := time.Now()
proxy.nacosServer.InjectSecurityInfo(request.GetHeaders(), security.BuildNamingResourceByRequest(request))
response, err := proxy.rpcClient.GetRpcClient().Request(request, int64(proxy.clientConfig.TimeoutMs))
monitor.GetNamingRequestMonitor(constant.GRPC, request.GetRequestType(), rpc_response.GetGrpcResponseStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
return response, err
}
// RegisterInstance ...
func (proxy *NamingGrpcProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
logger.Infof("register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instance))
proxy.eventListener.CacheInstanceForRedo(serviceName, groupName, instance)
instanceRequest := rpc_request.NewInstanceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, "registerInstance", instance)
response, err := proxy.requestToServer(instanceRequest)
if err != nil {
return false, err
}
return response.IsSuccess(), err
}
// BatchRegisterInstance ...
func (proxy *NamingGrpcProxy) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
logger.Infof("batch register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instances))
proxy.eventListener.CacheInstancesForRedo(serviceName, groupName, instances)
batchInstanceRequest := rpc_request.NewBatchInstanceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, "batchRegisterInstance", instances)
response, err := proxy.requestToServer(batchInstanceRequest)
if err != nil {
return false, err
}
return response.IsSuccess(), err
}
// DeregisterInstance ...
func (proxy *NamingGrpcProxy) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
logger.Infof("deregister instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
proxy.clientConfig.NamespaceId, serviceName, instance.Ip, instance.Port, instance.ClusterName)
instanceRequest := rpc_request.NewInstanceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, "deregisterInstance", instance)
response, err := proxy.requestToServer(instanceRequest)
proxy.eventListener.RemoveInstanceForRedo(serviceName, groupName, instance)
if err != nil {
return false, err
}
return response.IsSuccess(), err
}
// GetServiceList ...
func (proxy *NamingGrpcProxy) GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
var selectorStr string
if selector != nil {
switch selector.Type {
case "label":
selectorStr = util.ToJsonString(selector)
default:
break
}
}
response, err := proxy.requestToServer(rpc_request.NewServiceListRequest(namespaceId, "",
groupName, int(pageNo), int(pageSize), selectorStr))
if err != nil {
return model.ServiceList{}, err
}
serviceListResponse := response.(*rpc_response.ServiceListResponse)
return model.ServiceList{
Count: int64(serviceListResponse.Count),
Doms: serviceListResponse.ServiceNames,
}, nil
}
// ServerHealthy ...
func (proxy *NamingGrpcProxy) ServerHealthy() bool {
return proxy.rpcClient.GetRpcClient().IsRunning()
}
// QueryInstancesOfService ...
func (proxy *NamingGrpcProxy) QueryInstancesOfService(serviceName, groupName, cluster string, udpPort int, healthyOnly bool) (*model.Service, error) {
response, err := proxy.requestToServer(rpc_request.NewServiceQueryRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, cluster,
healthyOnly, udpPort))
if err != nil {
return nil, err
}
queryServiceResponse := response.(*rpc_response.QueryServiceResponse)
return &queryServiceResponse.ServiceInfo, nil
}
func (proxy *NamingGrpcProxy) IsSubscribed(serviceName, groupName string, clusters string) bool {
return proxy.eventListener.IsSubscriberCached(util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters))
}
// Subscribe ...
func (proxy *NamingGrpcProxy) Subscribe(serviceName, groupName string, clusters string) (model.Service, error) {
logger.Infof("Subscribe Service namespaceId:<%s>, serviceName:<%s>, groupName:<%s>, clusters:<%s>",
proxy.clientConfig.NamespaceId, serviceName, groupName, clusters)
proxy.eventListener.CacheSubscriberForRedo(util.GetGroupName(serviceName, groupName), clusters)
request := rpc_request.NewSubscribeServiceRequest(proxy.clientConfig.NamespaceId, serviceName,
groupName, clusters, true)
request.Headers["app"] = proxy.clientConfig.AppName
response, err := proxy.requestToServer(request)
if err != nil {
return model.Service{}, err
}
subscribeServiceResponse := response.(*rpc_response.SubscribeServiceResponse)
return subscribeServiceResponse.ServiceInfo, nil
}
// Unsubscribe ...
func (proxy *NamingGrpcProxy) Unsubscribe(serviceName, groupName, clusters string) error {
logger.Infof("Unsubscribe Service namespaceId:<%s>, serviceName:<%s>, groupName:<%s>, clusters:<%s>",
proxy.clientConfig.NamespaceId, serviceName, groupName, clusters)
proxy.eventListener.RemoveSubscriberForRedo(util.GetGroupName(serviceName, groupName), clusters)
_, err := proxy.requestToServer(rpc_request.NewSubscribeServiceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName,
clusters, false))
return err
}
func (proxy *NamingGrpcProxy) CloseClient() {
logger.Info("Close Nacos Go SDK Client...")
proxy.rpcClient.GetRpcClient().Shutdown()
}

View File

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

View File

@ -0,0 +1,160 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_http
import (
"context"
"fmt"
"net/http"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/buger/jsonparser"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"golang.org/x/sync/semaphore"
)
type BeatReactor struct {
ctx context.Context
beatMap cache.ConcurrentMap
nacosServer *nacos_server.NacosServer
beatThreadCount int
beatThreadSemaphore *semaphore.Weighted
beatRecordMap cache.ConcurrentMap
clientCfg constant.ClientConfig
mux *sync.Mutex
}
const DefaultBeatThreadNum = 20
func NewBeatReactor(ctx context.Context, clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer) BeatReactor {
br := BeatReactor{}
br.ctx = ctx
br.beatMap = cache.NewConcurrentMap()
br.nacosServer = nacosServer
br.clientCfg = clientCfg
br.beatThreadCount = DefaultBeatThreadNum
br.beatRecordMap = cache.NewConcurrentMap()
br.beatThreadSemaphore = semaphore.NewWeighted(int64(br.beatThreadCount))
br.mux = new(sync.Mutex)
return br
}
func buildKey(serviceName string, ip string, port uint64) string {
return serviceName + constant.NAMING_INSTANCE_ID_SPLITTER + ip + constant.NAMING_INSTANCE_ID_SPLITTER + strconv.Itoa(int(port))
}
func (br *BeatReactor) AddBeatInfo(serviceName string, beatInfo *model.BeatInfo) {
logger.Infof("adding beat: <%s> to beat map", util.ToJsonString(beatInfo))
k := buildKey(serviceName, beatInfo.Ip, beatInfo.Port)
defer br.mux.Unlock()
br.mux.Lock()
if data, ok := br.beatMap.Get(k); ok {
beatInfo = data.(*model.BeatInfo)
atomic.StoreInt32(&beatInfo.State, int32(model.StateShutdown))
br.beatMap.Remove(k)
}
br.beatMap.Set(k, beatInfo)
beatInfo.Metadata = util.DeepCopyMap(beatInfo.Metadata)
monitor.GetDom2BeatSizeMonitor().Set(float64(br.beatMap.Count()))
go br.sendInstanceBeat(k, beatInfo)
}
func (br *BeatReactor) RemoveBeatInfo(serviceName string, ip string, port uint64) {
logger.Infof("remove beat: %s@%s:%d from beat map", serviceName, ip, port)
k := buildKey(serviceName, ip, port)
defer br.mux.Unlock()
br.mux.Lock()
data, exist := br.beatMap.Get(k)
if exist {
beatInfo := data.(*model.BeatInfo)
atomic.StoreInt32(&beatInfo.State, int32(model.StateShutdown))
}
monitor.GetDom2BeatSizeMonitor().Set(float64(br.beatMap.Count()))
br.beatMap.Remove(k)
}
func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
t := time.NewTimer(beatInfo.Period)
defer t.Stop()
for {
br.beatThreadSemaphore.Acquire(br.ctx, 1)
//如果当前实例注销,则进行停止心跳
if atomic.LoadInt32(&beatInfo.State) == int32(model.StateShutdown) {
logger.Infof("instance[%s] stop heartBeating", k)
br.beatThreadSemaphore.Release(1)
return
}
//进行心跳通信
beatInterval, err := br.SendBeat(beatInfo)
if err != nil {
logger.Errorf("beat to server return error:%+v", err)
br.beatThreadSemaphore.Release(1)
t := time.NewTimer(beatInfo.Period)
<-t.C
continue
}
if beatInterval > 0 {
beatInfo.Period = time.Duration(time.Millisecond.Nanoseconds() * beatInterval)
}
br.beatRecordMap.Set(k, util.CurrentMillis())
br.beatThreadSemaphore.Release(1)
t.Reset(beatInfo.Period)
select {
case <-t.C:
case <-br.ctx.Done():
return
}
}
}
func (br *BeatReactor) SendBeat(info *model.BeatInfo) (int64, error) {
logger.Infof("namespaceId:<%s> sending beat to server:<%s>",
br.clientCfg.NamespaceId, util.ToJsonString(info))
params := map[string]string{}
params["namespaceId"] = br.clientCfg.NamespaceId
params["serviceName"] = info.ServiceName
params["beat"] = util.ToJsonString(info)
api := constant.SERVICE_BASE_PATH + "/instance/beat"
result, err := br.nacosServer.ReqApi(api, params, http.MethodPut, br.clientCfg)
if err != nil {
return 0, err
}
if result != "" {
interVal, err := jsonparser.GetInt([]byte(result), "clientBeatInterval")
if err != nil {
return 0, errors.New(fmt.Sprintf("namespaceId:<%s> sending beat to server:<%s> get 'clientBeatInterval' from <%s> error:<%+v>", br.clientCfg.NamespaceId, util.ToJsonString(info), result, err))
} else {
return interVal, nil
}
}
return 0, nil
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_http
import (
"context"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/stretchr/testify/assert"
)
func TestBeatReactor_AddBeatInfo(t *testing.T) {
br := NewBeatReactor(context.Background(), constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo := &model.BeatInfo{
Ip: "127.0.0.1",
Port: 8080,
Metadata: map[string]string{},
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo)
key := buildKey(util.GetGroupName(serviceName, groupName), beatInfo.Ip, beatInfo.Port)
result, ok := br.beatMap.Get(key)
assert.Equal(t, ok, true, "key should exists!")
assert.ObjectsAreEqual(result.(*model.BeatInfo), beatInfo)
}
func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
br := NewBeatReactor(context.Background(), constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo1 := &model.BeatInfo{
Ip: "127.0.0.1",
Port: 8080,
Metadata: map[string]string{},
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo1)
beatInfo2 := &model.BeatInfo{
Ip: "127.0.0.2",
Port: 8080,
Metadata: map[string]string{},
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo2)
br.RemoveBeatInfo(util.GetGroupName(serviceName, groupName), "127.0.0.1", 8080)
key := buildKey(util.GetGroupName(serviceName, groupName), beatInfo2.Ip, beatInfo2.Port)
result, ok := br.beatMap.Get(key)
assert.Equal(t, br.beatMap.Count(), 1, "beatinfo map length should be 1")
assert.Equal(t, ok, true, "key should exists!")
assert.ObjectsAreEqual(result.(*model.BeatInfo), beatInfo2)
}

View File

@ -0,0 +1,218 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_http
import (
"context"
"net/http"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/buger/jsonparser"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
// NamingHttpProxy ...
type NamingHttpProxy struct {
clientConfig constant.ClientConfig
nacosServer *nacos_server.NacosServer
beatReactor BeatReactor
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
// NewNamingHttpProxy create naming http proxy
func NewNamingHttpProxy(ctx context.Context, clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer,
serviceInfoHolder *naming_cache.ServiceInfoHolder) (*NamingHttpProxy, error) {
srvProxy := NamingHttpProxy{
clientConfig: clientCfg,
nacosServer: nacosServer,
serviceInfoHolder: serviceInfoHolder,
}
srvProxy.beatReactor = NewBeatReactor(ctx, clientCfg, nacosServer)
NewPushReceiver(ctx, serviceInfoHolder).startServer()
return &srvProxy, nil
}
// RegisterInstance ...
func (proxy *NamingHttpProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
logger.Infof("register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instance))
serviceName = util.GetGroupName(serviceName, groupName)
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["groupName"] = groupName
params["app"] = proxy.clientConfig.AppName
params["clusterName"] = instance.ClusterName
params["ip"] = instance.Ip
params["port"] = strconv.Itoa(int(instance.Port))
params["weight"] = strconv.FormatFloat(instance.Weight, 'f', -1, 64)
params["enable"] = strconv.FormatBool(instance.Enable)
params["healthy"] = strconv.FormatBool(instance.Healthy)
params["metadata"] = util.ToJsonString(instance.Metadata)
params["ephemeral"] = strconv.FormatBool(instance.Ephemeral)
_, err := proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodPost, proxy.clientConfig)
if err != nil {
return false, err
}
if instance.Ephemeral {
beatInfo := &model.BeatInfo{
Ip: instance.Ip,
Port: instance.Port,
Metadata: instance.Metadata,
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: instance.ClusterName,
Weight: instance.Weight,
Period: util.GetDurationWithDefault(instance.Metadata, constant.HEART_BEAT_INTERVAL, time.Second*5),
State: model.StateRunning,
}
proxy.beatReactor.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo)
}
return true, nil
}
func (proxy *NamingHttpProxy) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
panic("implement me")
}
// DeregisterInstance ...
func (proxy *NamingHttpProxy) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
serviceName = util.GetGroupName(serviceName, groupName)
logger.Infof("deregister instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
proxy.clientConfig.NamespaceId, serviceName, instance.Ip, instance.Port, instance.ClusterName)
proxy.beatReactor.RemoveBeatInfo(serviceName, instance.Ip, instance.Port)
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["clusterName"] = instance.ClusterName
params["ip"] = instance.Ip
params["port"] = strconv.Itoa(int(instance.Port))
params["ephemeral"] = strconv.FormatBool(instance.Ephemeral)
_, err := proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodDelete, proxy.clientConfig)
if err != nil {
return false, err
}
return true, nil
}
// GetServiceList ...
func (proxy *NamingHttpProxy) GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
params := map[string]string{}
params["namespaceId"] = namespaceId
params["groupName"] = groupName
params["pageNo"] = strconv.Itoa(int(pageNo))
params["pageSize"] = strconv.Itoa(int(pageSize))
if selector != nil {
switch selector.Type {
case "label":
params["selector"] = util.ToJsonString(selector)
break
default:
break
}
}
serviceList := model.ServiceList{}
api := constant.SERVICE_BASE_PATH + "/service/list"
result, err := proxy.nacosServer.ReqApi(api, params, http.MethodGet, proxy.clientConfig)
if err != nil {
return serviceList, err
}
if result == "" {
return serviceList, errors.New("request server return empty")
}
count, err := jsonparser.GetInt([]byte(result), "count")
if err != nil {
return serviceList, errors.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'count' from <%s> error:<%+v>", namespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err)
}
var doms []string
_, err = jsonparser.ArrayEach([]byte(result), func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
doms = append(doms, string(value))
}, "doms")
if err != nil {
return serviceList, errors.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'doms' from <%s> error:<%+v> ", namespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err)
}
serviceList.Count = count
serviceList.Doms = doms
return serviceList, nil
}
// ServerHealthy ...
func (proxy *NamingHttpProxy) ServerHealthy() bool {
api := constant.SERVICE_BASE_PATH + "/operator/metrics"
result, err := proxy.nacosServer.ReqApi(api, map[string]string{}, http.MethodGet, proxy.clientConfig)
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
return false
}
if result != "" {
status, err := jsonparser.GetString([]byte(result), "status")
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
} else {
return status == "UP"
}
}
return false
}
// QueryInstancesOfService ...
func (proxy *NamingHttpProxy) QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
param := make(map[string]string)
param["namespaceId"] = proxy.clientConfig.NamespaceId
param["serviceName"] = util.GetGroupName(serviceName, groupName)
param["app"] = proxy.clientConfig.AppName
param["clusters"] = clusters
param["udpPort"] = strconv.Itoa(udpPort)
param["healthyOnly"] = strconv.FormatBool(healthyOnly)
param["clientIP"] = util.LocalIP()
api := constant.SERVICE_PATH + "/list"
result, err := proxy.nacosServer.ReqApi(api, param, http.MethodGet, proxy.clientConfig)
if err != nil {
return nil, err
}
return util.JsonToService(result), nil
}
// Subscribe ...
func (proxy *NamingHttpProxy) Subscribe(serviceName, groupName, clusters string) (model.Service, error) {
return model.Service{}, nil
}
// Unsubscribe ...
func (proxy *NamingHttpProxy) Unsubscribe(serviceName, groupName, clusters string) error {
return nil
}
func (proxy *NamingHttpProxy) CloseClient() {
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_http
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"io"
"math/rand"
"net"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type PushReceiver struct {
ctx context.Context
port int
host string
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
type PushData struct {
PushType string `json:"type"`
Data string `json:"data"`
LastRefTime int64 `json:"lastRefTime"`
}
var (
GZIP_MAGIC = []byte("\x1F\x8B")
)
func NewPushReceiver(ctx context.Context, serviceInfoHolder *naming_cache.ServiceInfoHolder) *PushReceiver {
pr := PushReceiver{
ctx: ctx,
serviceInfoHolder: serviceInfoHolder,
}
return &pr
}
func (us *PushReceiver) tryListen() (*net.UDPConn, bool) {
addr, err := net.ResolveUDPAddr("udp", us.host+":"+strconv.Itoa(us.port))
if err != nil {
logger.Errorf("can't resolve address,err: %+v", err)
return nil, false
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
logger.Errorf("error listening %s:%d,err:%+v", us.host, us.port, err)
return nil, false
}
return conn, true
}
func (us *PushReceiver) startServer() {
var (
conn *net.UDPConn
ok bool
)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 3; i++ {
port := r.Intn(1000) + 54951
us.port = port
conn, ok = us.tryListen()
if ok {
logger.Infof("udp server start, port: " + strconv.Itoa(port))
break
}
if !ok && i == 2 {
logger.Errorf("failed to start udp server after trying 3 times.")
}
}
if conn == nil {
return
}
go func() {
defer conn.Close()
for {
select {
case <-us.ctx.Done():
return
default:
us.handleClient(conn)
}
}
}()
}
func (us *PushReceiver) handleClient(conn *net.UDPConn) {
data := make([]byte, 4024)
n, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
logger.Errorf("failed to read UDP msg because of %+v", err)
return
}
s := TryDecompressData(data[:n])
logger.Info("receive push: "+s+" from: ", remoteAddr)
var pushData PushData
err1 := json.Unmarshal([]byte(s), &pushData)
if err1 != nil {
logger.Infof("failed to process push data.err:%+v", err1)
return
}
ack := make(map[string]string)
if pushData.PushType == "dom" || pushData.PushType == "service" {
us.serviceInfoHolder.ProcessServiceJson(pushData.Data)
ack["type"] = "push-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = ""
} else if pushData.PushType == "dump" {
ack["type"] = "dump-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = util.ToJsonString(us.serviceInfoHolder.ServiceInfoMap)
} else {
ack["type"] = "unknow-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = ""
}
bs, _ := json.Marshal(ack)
c, err := conn.WriteToUDP(bs, remoteAddr)
if err != nil {
logger.Errorf("WriteToUDP failed,return:%d,err:%+v", c, err)
}
}
func TryDecompressData(data []byte) string {
if !IsGzipFile(data) {
return string(data)
}
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
logger.Errorf("failed to decompress gzip data,err:%+v", err)
return ""
}
defer reader.Close()
bs, err := io.ReadAll(reader)
if err != nil {
logger.Errorf("failed to decompress gzip data,err:%+v", err)
return ""
}
return string(bs)
}
func IsGzipFile(data []byte) bool {
if len(data) < 2 {
return false
}
return bytes.HasPrefix(data, GZIP_MAGIC)
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"math/rand"
"sort"
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
type Chooser struct {
data []model.Instance
totals []int
max int
}
type instance []model.Instance
func (a instance) Len() int {
return len(a)
}
func (a instance) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a instance) Less(i, j int) bool {
return a[i].Weight < a[j].Weight
}
// NewChooser initializes a new Chooser for picking from the provided Choices.
func newChooser(instances []model.Instance) Chooser {
sort.Sort(instance(instances))
totals := make([]int, len(instances))
runningTotal := 0
for i, c := range instances {
runningTotal += int(c.Weight)
totals[i] = runningTotal
}
return Chooser{data: instances, totals: totals, max: runningTotal}
}
func (chs Chooser) pick() model.Instance {
r := rand.Intn(chs.max) + 1
i := sort.SearchInts(chs.totals, r)
return chs.data[i]
}

View File

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

View File

@ -0,0 +1,42 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_proxy
import (
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
// INamingProxy ...
type INamingProxy interface {
RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error)
BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error)
DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error)
GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error)
ServerHealthy() bool
QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error)
Subscribe(serviceName, groupName, clusters string) (model.Service, error)
Unsubscribe(serviceName, groupName, clusters string) error
CloseClient()
}

View File

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

View File

@ -0,0 +1,139 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"context"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_grpc"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_http"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
// NamingProxyDelegate ...
type NamingProxyDelegate struct {
httpClientProxy *naming_http.NamingHttpProxy
grpcClientProxy *naming_grpc.NamingGrpcProxy
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
func NewNamingProxyDelegate(ctx context.Context, clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig,
httpAgent http_agent.IHttpAgent, serviceInfoHolder *naming_cache.ServiceInfoHolder) (naming_proxy.INamingProxy, error) {
return NewNamingProxyDelegateWithRamCredentialProvider(ctx, clientCfg, serverCfgs, httpAgent, serviceInfoHolder, nil)
}
func NewNamingProxyDelegateWithRamCredentialProvider(ctx context.Context, clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig,
httpAgent http_agent.IHttpAgent, serviceInfoHolder *naming_cache.ServiceInfoHolder, provider security.RamCredentialProvider) (naming_proxy.INamingProxy, error) {
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
namingHeader := map[string][]string{
"Client-Version": {constant.CLIENT_VERSION},
"User-Agent": {constant.CLIENT_VERSION},
"RequestId": {uid.String()},
"Request-Module": {"Naming"},
}
nacosServer, err := nacos_server.NewNacosServerWithRamCredentialProvider(ctx, serverCfgs, clientCfg, httpAgent, clientCfg.TimeoutMs, clientCfg.Endpoint, namingHeader, provider)
if err != nil {
return nil, err
}
httpClientProxy, err := naming_http.NewNamingHttpProxy(ctx, clientCfg, nacosServer, serviceInfoHolder)
if err != nil {
return nil, err
}
grpcClientProxy, err := naming_grpc.NewNamingGrpcProxy(ctx, clientCfg, nacosServer, serviceInfoHolder)
if err != nil {
return nil, err
}
return &NamingProxyDelegate{
httpClientProxy: httpClientProxy,
grpcClientProxy: grpcClientProxy,
serviceInfoHolder: serviceInfoHolder,
}, nil
}
func (proxy *NamingProxyDelegate) getExecuteClientProxy(instance model.Instance) (namingProxy naming_proxy.INamingProxy) {
if instance.Ephemeral {
namingProxy = proxy.grpcClientProxy
} else {
namingProxy = proxy.httpClientProxy
}
return namingProxy
}
func (proxy *NamingProxyDelegate) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return proxy.getExecuteClientProxy(instance).RegisterInstance(serviceName, groupName, instance)
}
func (proxy *NamingProxyDelegate) BatchRegisterInstance(serviceName string, groupName string, instances []model.Instance) (bool, error) {
return proxy.grpcClientProxy.BatchRegisterInstance(serviceName, groupName, instances)
}
func (proxy *NamingProxyDelegate) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return proxy.getExecuteClientProxy(instance).DeregisterInstance(serviceName, groupName, instance)
}
func (proxy *NamingProxyDelegate) GetServiceList(pageNo uint32, pageSize uint32, groupName, namespaceId string, selector *model.ExpressionSelector) (model.ServiceList, error) {
return proxy.grpcClientProxy.GetServiceList(pageNo, pageSize, groupName, namespaceId, selector)
}
func (proxy *NamingProxyDelegate) ServerHealthy() bool {
return proxy.grpcClientProxy.ServerHealthy() || proxy.httpClientProxy.ServerHealthy()
}
func (proxy *NamingProxyDelegate) QueryInstancesOfService(serviceName, groupName, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
return proxy.grpcClientProxy.QueryInstancesOfService(serviceName, groupName, clusters, udpPort, healthyOnly)
}
func (proxy *NamingProxyDelegate) Subscribe(serviceName, groupName string, clusters string) (model.Service, error) {
var err error
isSubscribed := proxy.grpcClientProxy.IsSubscribed(serviceName, groupName, clusters)
serviceNameWithGroup := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
serviceInfo, ok := proxy.serviceInfoHolder.ServiceInfoMap.Load(serviceNameWithGroup)
if !isSubscribed || !ok {
serviceInfo, err = proxy.grpcClientProxy.Subscribe(serviceName, groupName, clusters)
if err != nil {
return model.Service{}, err
}
}
service := serviceInfo.(model.Service)
proxy.serviceInfoHolder.ProcessService(&service)
return service, nil
}
func (proxy *NamingProxyDelegate) Unsubscribe(serviceName, groupName, clusters string) error {
proxy.serviceInfoHolder.StopUpdateIfContain(util.GetGroupName(serviceName, groupName), clusters)
return proxy.grpcClientProxy.Unsubscribe(serviceName, groupName, clusters)
}
func (proxy *NamingProxyDelegate) CloseClient() {
proxy.grpcClientProxy.CloseClient()
}

View File

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

View File

@ -0,0 +1,84 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"context"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type ServiceInfoUpdater struct {
ctx context.Context
serviceInfoHolder *naming_cache.ServiceInfoHolder
updateThreadNum int
namingProxy naming_proxy.INamingProxy
}
func NewServiceInfoUpdater(ctx context.Context, serviceInfoHolder *naming_cache.ServiceInfoHolder, updateThreadNum int,
namingProxy naming_proxy.INamingProxy) *ServiceInfoUpdater {
return &ServiceInfoUpdater{
ctx: ctx,
serviceInfoHolder: serviceInfoHolder,
updateThreadNum: updateThreadNum,
namingProxy: namingProxy,
}
}
func (s *ServiceInfoUpdater) asyncUpdateService() {
sema := util.NewSemaphore(s.updateThreadNum)
for {
select {
case <-s.ctx.Done():
return
default:
s.serviceInfoHolder.ServiceInfoMap.Range(func(key, value interface{}) bool {
service := value.(model.Service)
lastRefTime, ok := s.serviceInfoHolder.UpdateTimeMap.Load(util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName),
service.Clusters))
if !ok {
lastRefTime = uint64(0)
}
if uint64(util.CurrentMillis())-lastRefTime.(uint64) > service.CacheMillis {
sema.Acquire()
go func() {
defer sema.Release()
s.updateServiceNow(service.Name, service.GroupName, service.Clusters)
}()
}
return true
})
time.Sleep(1 * time.Second)
}
}
}
func (s *ServiceInfoUpdater) updateServiceNow(serviceName, groupName, clusters string) {
result, err := s.namingProxy.QueryInstancesOfService(serviceName, groupName, clusters, 0, false)
if err != nil {
logger.Errorf("QueryInstances error, serviceName:%s, cluster:%s, err:%v", serviceName, clusters, err)
return
}
s.serviceInfoHolder.ProcessService(result)
}

View File

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

View File

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

View File

@ -0,0 +1,246 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package constant
import (
"os"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
)
func NewClientConfig(opts ...ClientOption) *ClientConfig {
clientConfig := &ClientConfig{
TimeoutMs: 10 * 1000,
BeatInterval: 5 * 1000,
OpenKMS: false,
CacheDir: file.GetCurrentPath() + string(os.PathSeparator) + "cache",
UpdateThreadNum: 20,
NotLoadCacheAtStart: false,
UpdateCacheWhenEmpty: false,
LogDir: file.GetCurrentPath() + string(os.PathSeparator) + "log",
LogLevel: "info",
}
for _, opt := range opts {
opt(clientConfig)
}
return clientConfig
}
// ClientOption ...
type ClientOption func(*ClientConfig)
// WithTimeoutMs ...
func WithTimeoutMs(timeoutMs uint64) ClientOption {
return func(config *ClientConfig) {
config.TimeoutMs = timeoutMs
}
}
// WithAppName ...
func WithAppName(appName string) ClientOption {
return func(config *ClientConfig) {
config.AppName = appName
}
}
// WithBeatInterval ...
func WithBeatInterval(beatInterval int64) ClientOption {
return func(config *ClientConfig) {
config.BeatInterval = beatInterval
}
}
// WithNamespaceId ...
func WithNamespaceId(namespaceId string) ClientOption {
return func(config *ClientConfig) {
config.NamespaceId = namespaceId
}
}
// WithEndpoint ...
func WithEndpoint(endpoint string) ClientOption {
return func(config *ClientConfig) {
config.Endpoint = endpoint
}
}
// WithEndpointContextPath ...
func WithEndpointContextPath(endpointContextPath string) ClientOption {
return func(config *ClientConfig) {
config.EndpointContextPath = endpointContextPath
}
}
// WithEndpointQueryParams ...
func WithEndpointQueryParams(endpointQueryPrams string) ClientOption {
return func(config *ClientConfig) {
config.EndpointQueryParams = endpointQueryPrams
}
}
// WithClusterName ...
func WithClusterName(clusterName string) ClientOption {
return func(config *ClientConfig) {
config.ClusterName = clusterName
}
}
// WithRegionId ...
func WithRegionId(regionId string) ClientOption {
return func(config *ClientConfig) {
config.RegionId = regionId
}
}
// WithAccessKey ...
func WithAccessKey(accessKey string) ClientOption {
return func(config *ClientConfig) {
config.AccessKey = accessKey
}
}
// WithSecretKey ...
func WithSecretKey(secretKey string) ClientOption {
return func(config *ClientConfig) {
config.SecretKey = secretKey
}
}
func WithRamConfig(ramConfig *RamConfig) ClientOption {
return func(config *ClientConfig) {
config.RamConfig = ramConfig
}
}
// WithOpenKMS ...
func WithOpenKMS(openKMS bool) ClientOption {
return func(config *ClientConfig) {
config.OpenKMS = openKMS
}
}
// WithOpenKMS ...
func WithKMSVersion(kmsVersion KMSVersion) ClientOption {
return func(config *ClientConfig) {
config.KMSVersion = kmsVersion
}
}
func WithKMSv3Config(kmsv3Config *KMSv3Config) ClientOption {
return func(config *ClientConfig) {
config.KMSv3Config = kmsv3Config
}
}
func WithKMSConfig(kmsConfig *KMSConfig) ClientOption {
return func(config *ClientConfig) {
config.KMSConfig = kmsConfig
}
}
// WithCacheDir ...
func WithCacheDir(cacheDir string) ClientOption {
return func(config *ClientConfig) {
config.CacheDir = cacheDir
}
}
// WithDisableUseSnapShot ...
func WithDisableUseSnapShot(disableUseSnapShot bool) ClientOption {
return func(config *ClientConfig) {
config.DisableUseSnapShot = disableUseSnapShot
}
}
// WithUpdateThreadNum ...
func WithUpdateThreadNum(updateThreadNum int) ClientOption {
return func(config *ClientConfig) {
config.UpdateThreadNum = updateThreadNum
}
}
// WithNotLoadCacheAtStart ...
func WithNotLoadCacheAtStart(notLoadCacheAtStart bool) ClientOption {
return func(config *ClientConfig) {
config.NotLoadCacheAtStart = notLoadCacheAtStart
}
}
// WithUpdateCacheWhenEmpty ...
func WithUpdateCacheWhenEmpty(updateCacheWhenEmpty bool) ClientOption {
return func(config *ClientConfig) {
config.UpdateCacheWhenEmpty = updateCacheWhenEmpty
}
}
// WithUsername ...
func WithUsername(username string) ClientOption {
return func(config *ClientConfig) {
config.Username = username
}
}
// WithPassword ...
func WithPassword(password string) ClientOption {
return func(config *ClientConfig) {
config.Password = password
}
}
// WithLogDir ...
func WithLogDir(logDir string) ClientOption {
return func(config *ClientConfig) {
config.LogDir = logDir
}
}
// WithLogLevel ...
func WithLogLevel(logLevel string) ClientOption {
return func(config *ClientConfig) {
config.LogLevel = logLevel
}
}
// WithLogSampling ...
func WithLogSampling(tick time.Duration, initial int, thereafter int) ClientOption {
return func(config *ClientConfig) {
config.LogSampling = &ClientLogSamplingConfig{initial, thereafter, tick}
}
}
// WithLogRollingConfig ...
func WithLogRollingConfig(rollingConfig *ClientLogRollingConfig) ClientOption {
return func(config *ClientConfig) {
config.LogRollingConfig = rollingConfig
}
}
func WithTLS(tlsCfg TLSConfig) ClientOption {
return func(config *ClientConfig) {
tlsCfg.Appointed = true
config.TLSCfg = tlsCfg
}
}
func WithAppConnLabels(appConnLabels map[string]string) ClientOption {
return func(config *ClientConfig) {
config.AppConnLabels = appConnLabels
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package constant
import (
"os"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/stretchr/testify/assert"
)
func TestNewClientConfig(t *testing.T) {
config := NewClientConfig()
assert.Equal(t, config.TimeoutMs, uint64(10000))
assert.Equal(t, config.Endpoint, "")
assert.Equal(t, config.LogLevel, "info")
assert.Equal(t, config.BeatInterval, int64(5000))
assert.Equal(t, config.UpdateThreadNum, 20)
assert.Equal(t, config.LogDir, file.GetCurrentPath()+string(os.PathSeparator)+"log")
assert.Equal(t, config.CacheDir, file.GetCurrentPath()+string(os.PathSeparator)+"cache")
assert.Equal(t, config.NotLoadCacheAtStart, false)
assert.Equal(t, config.UpdateCacheWhenEmpty, false)
assert.Equal(t, config.Username, "")
assert.Equal(t, config.Password, "")
assert.Equal(t, config.OpenKMS, false)
assert.Equal(t, config.NamespaceId, "")
assert.Equal(t, config.Username, "")
assert.Equal(t, config.RegionId, "")
assert.Equal(t, config.AccessKey, "")
assert.Equal(t, config.SecretKey, "")
}
func TestNewClientConfigWithOptions(t *testing.T) {
config := NewClientConfig(
WithTimeoutMs(uint64(20000)),
WithEndpoint("http://console.nacos.io:80"),
WithLogLevel("error"),
WithBeatInterval(int64(2000)),
WithUpdateThreadNum(30),
WithLogDir("/tmp/nacos/log"),
WithCacheDir("/tmp/nacos/cache"),
WithNotLoadCacheAtStart(true),
WithUpdateCacheWhenEmpty(true),
WithUsername("nacos"),
WithPassword("nacos"),
WithOpenKMS(true),
WithRegionId("shanghai"),
WithNamespaceId("namespace_1"),
WithAccessKey("accessKey_1"),
WithSecretKey("secretKey_1"),
)
assert.Equal(t, config.TimeoutMs, uint64(20000))
assert.Equal(t, config.Endpoint, "http://console.nacos.io:80")
assert.Equal(t, config.LogLevel, "error")
assert.Equal(t, config.BeatInterval, int64(2000))
assert.Equal(t, config.UpdateThreadNum, 30)
assert.Equal(t, config.LogDir, "/tmp/nacos/log")
assert.Equal(t, config.CacheDir, "/tmp/nacos/cache")
assert.Equal(t, config.NotLoadCacheAtStart, true)
assert.Equal(t, config.UpdateCacheWhenEmpty, true)
assert.Equal(t, config.Username, "nacos")
assert.Equal(t, config.Password, "nacos")
assert.Equal(t, config.OpenKMS, true)
assert.Equal(t, config.RegionId, "shanghai")
assert.Equal(t, config.NamespaceId, "namespace_1")
assert.Equal(t, config.AccessKey, "accessKey_1")
assert.Equal(t, config.SecretKey, "secretKey_1")
}

View File

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

View File

@ -1,60 +1,121 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package constant
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-07 15:13
**/
import "time"
type KMSVersion string
const (
KEY_ENDPOINT = "endpoint"
KEY_NAME_SPACE = "namespace"
KEY_ACCESS_KEY = "accessKey"
KEY_SECRET_KEY = "secretKey"
KEY_SERVER_ADDR = "serverAddr"
KEY_CONTEXT_PATH = "contextPath"
KEY_ENCODE = "encode"
KEY_DATA_ID = "dataId"
KEY_GROUP = "group"
KEY_TENANT = "tenant"
KEY_DESC = "desc"
KEY_APP_NAME = "appName"
KEY_CONTENT = "content"
KEY_TIMEOUT_MS = "timeoutMs"
KEY_LISTEN_INTERVAL = "listenInterval"
KEY_SERVER_CONFIGS = "serverConfigs"
KEY_CLIENT_CONFIG = "clientConfig"
WEB_CONTEXT = "/nacos"
CONFIG_BASE_PATH = "/v1/cs"
CONFIG_PATH = CONFIG_BASE_PATH + "/configs"
CONFIG_LISTEN_PATH = CONFIG_BASE_PATH + "/configs/listener"
SERVICE_BASE_PATH = "/v1/ns"
SERVICE_PATH = SERVICE_BASE_PATH + "/instance"
SERVICE_SUBSCRIBE_PATH = SERVICE_PATH + "/list"
NAMESPACE_PATH = "/v1/console/namespaces"
SPLIT_CONFIG = string(rune(1))
SPLIT_CONFIG_INNER = string(rune(2))
KEY_LISTEN_CONFIGS = "Listening-Configs"
KEY_SERVICE_NAME = "serviceName"
KEY_IP = "ip"
KEY_PORT = "port"
KEY_WEIGHT = "weight"
KEY_ENABLE = "enable"
KEY_HEALTHY = "healthy"
KEY_METADATA = "metadata"
KEY_CLUSTER_NAME = "clusterName"
KEY_CLUSTER = "cluster"
KEY_BEAT = "beat"
KEY_DOM = "dom"
DEFAULT_CONTEXT_PATH = "/nacos"
CLIENT_VERSION = "Nacos-go-Client:v1.0.0"
REQUEST_DOMAIN_RETRY_TIME = 3
SERVICE_INFO_SPLITER = "@@"
CONFIG_INFO_SPLITER = "@@"
DEFAULT_NAMESPACE_ID = "public"
DEFAULT_GROUP = "DEFAULT_GROUP"
NAMING_INSTANCE_ID_SPLITTER = "#"
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

@ -0,0 +1,23 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package constant
const (
HEART_BEAT_TIMEOUT = "preserved.heart.beat.timeout"
IP_DELETE_TIMEOUT = "preserved.ip.delete.timeout"
HEART_BEAT_INTERVAL = "preserved.heart.beat.interval"
)

View File

@ -0,0 +1,70 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package constant
func NewServerConfig(ipAddr string, port uint64, opts ...ServerOption) *ServerConfig {
serverConfig := &ServerConfig{
IpAddr: ipAddr,
Port: port,
ContextPath: DEFAULT_CONTEXT_PATH,
Scheme: DEFAULT_SERVER_SCHEME,
}
for _, opt := range opts {
opt(serverConfig)
}
return serverConfig
}
// ServerOption ...
type ServerOption func(*ServerConfig)
// WithScheme set Scheme for server
func WithScheme(scheme string) ServerOption {
return func(config *ServerConfig) {
config.Scheme = scheme
}
}
// WithContextPath set contextPath for server
func WithContextPath(contextPath string) ServerOption {
return func(config *ServerConfig) {
config.ContextPath = contextPath
}
}
// WithIpAddr set ip address for server
func WithIpAddr(ipAddr string) ServerOption {
return func(config *ServerConfig) {
config.IpAddr = ipAddr
}
}
// WithPort set port for server
func WithPort(port uint64) ServerOption {
return func(config *ServerConfig) {
config.Port = port
}
}
// WithGrpcPort set grpc port for server
func WithGrpcPort(port uint64) ServerOption {
return func(config *ServerConfig) {
config.GrpcPort = port
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package constant
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewServerConfig(t *testing.T) {
config := NewServerConfig("console.nacos.io", 80)
assert.Equal(t, "console.nacos.io", config.IpAddr)
assert.Equal(t, uint64(80), config.Port)
assert.Equal(t, "/nacos", config.ContextPath)
assert.Equal(t, "http", config.Scheme)
assert.True(t, config.Port > 0 && config.Port < 65535)
}
func TestNewServerConfigWithOptions(t *testing.T) {
config := NewServerConfig(
"console.nacos.io",
80,
WithContextPath("/ns"),
WithScheme("https"),
)
assert.Equal(t, "console.nacos.io", config.IpAddr)
assert.Equal(t, uint64(80), config.Port)
assert.Equal(t, "/ns", config.ContextPath)
assert.Equal(t, "https", config.Scheme)
assert.True(t, config.Port > 0 && config.Port < 65535)
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package constant
var SkipVerifyConfig = TLSConfig{Enable: true}
func NewTLSConfig(opts ...TLSOption) *TLSConfig {
tlsConfig := TLSConfig{Enable: true}
for _, opt := range opts {
opt(&tlsConfig)
}
return &tlsConfig
}
type TLSOption func(*TLSConfig)
func WithCA(caFile, serverNameOverride string) TLSOption {
return func(tc *TLSConfig) {
tc.CaFile = caFile
tc.ServerNameOverride = serverNameOverride
}
}
func WithCertificate(certFile, keyFile string) TLSOption {
return func(tc *TLSConfig) {
tc.CertFile = certFile
tc.KeyFile = keyFile
}
}

View File

@ -0,0 +1,54 @@
package constant
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewTLSConfigWithOptions(t *testing.T) {
t.Run("TestNoOption", func(t *testing.T) {
cfg := SkipVerifyConfig
assert.Equal(t, "", cfg.CaFile)
assert.Equal(t, "", cfg.CertFile)
assert.Equal(t, "", cfg.KeyFile)
assert.Equal(t, "", cfg.ServerNameOverride)
})
t.Run("TestCAOption", func(t *testing.T) {
cfg := NewTLSConfig(
WithCA("ca", "host"),
)
assert.Equal(t, "ca", cfg.CaFile)
assert.Equal(t, "", cfg.CertFile)
assert.Equal(t, "", cfg.KeyFile)
assert.Equal(t, "host", cfg.ServerNameOverride)
})
t.Run("TestCertOption", func(t *testing.T) {
cfg := NewTLSConfig(
WithCA("ca", "host"),
WithCertificate("cert", "key"),
)
assert.Equal(t, "ca", cfg.CaFile)
assert.Equal(t, "cert", cfg.CertFile)
assert.Equal(t, "key", cfg.KeyFile)
assert.Equal(t, "host", cfg.ServerNameOverride)
})
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encoding
import (
"encoding/base64"
"unicode/utf8"
)
func DecodeString2Utf8Bytes(data string) []byte {
resBytes := make([]byte, 0, 8)
if len(data) == 0 {
return resBytes
}
bytesLen := 0
runes := []rune(data)
for _, r := range runes {
bytesLen += utf8.RuneLen(r)
}
resBytes = make([]byte, bytesLen)
pos := 0
for _, r := range runes {
pos += utf8.EncodeRune(resBytes[pos:], r)
}
return resBytes
}
func EncodeUtf8Bytes2String(bytes []byte) string {
if len(bytes) == 0 {
return ""
}
var startPos, endPos int
resRunes := make([]rune, 0, 8)
for endPos <= len(bytes) {
if utf8.FullRune(bytes[startPos:endPos]) {
decodedRune, _ := utf8.DecodeRune(bytes[startPos:endPos])
resRunes = append(resRunes, decodedRune)
startPos = endPos
}
endPos++
}
return string(resRunes)
}
func DecodeBase64(bytes []byte) ([]byte, error) {
dst := make([]byte, base64.StdEncoding.DecodedLen(len(bytes)))
n, err := base64.StdEncoding.Decode(dst, bytes)
if err != nil {
return nil, err
}
return dst[:n], nil
}
func EncodeBase64(bytes []byte) ([]byte, error) {
dst := make([]byte, base64.StdEncoding.EncodedLen(len(bytes)))
base64.StdEncoding.Encode(dst, bytes)
return dst[:], nil
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encryption
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
)
func AesEcbPkcs5PaddingEncrypt(plainContent, key []byte) (retBytes []byte, err error) {
if len(plainContent) == 0 {
return nil, nil
}
aesCipherBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
pkcs5PaddingBytes := PKCS5Padding(plainContent, aesCipherBlock.BlockSize())
return BlockEncrypt(pkcs5PaddingBytes, aesCipherBlock)
}
func AesEcbPkcs5PaddingDecrypt(cipherContent, key []byte) (retBytes []byte, err error) {
if len(cipherContent) == 0 {
return nil, nil
}
aesCipherBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decryptBytes, err := BlockDecrypt(cipherContent, aesCipherBlock)
if err != nil {
return nil, err
}
retBytes = PKCS5UnPadding(decryptBytes)
return retBytes, nil
}
func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func BlockEncrypt(src []byte, b cipher.Block) (dst []byte, err error) {
if len(src)%b.BlockSize() != 0 {
return nil, fmt.Errorf("input not full blocks")
}
buf := make([]byte, b.BlockSize())
for i := 0; i < len(src); i += b.BlockSize() {
b.Encrypt(buf, src[i:i+b.BlockSize()])
dst = append(dst, buf...)
}
return
}
func BlockDecrypt(src []byte, b cipher.Block) (dst []byte, err error) {
if len(src)%b.BlockSize() != 0 {
return nil, fmt.Errorf("input not full blocks")
}
buf := make([]byte, b.BlockSize())
for i := 0; i < len(src); i += b.BlockSize() {
b.Decrypt(buf, src[i:i+b.BlockSize()])
dst = append(dst, buf...)
}
return
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encryption
import "fmt"
const (
CipherPrefix = "cipher-"
KmsAes128AlgorithmName = "cipher-kms-aes-128"
KmsAes256AlgorithmName = "cipher-kms-aes-256"
KmsAlgorithmName = "cipher"
kmsAes128KeySpec = "AES_128"
kmsAes256KeySpec = "AES_256"
kmsScheme = "https"
kmsAcceptFormat = "XML"
kmsCipherAlgorithm = "AES/ECB/PKCS5Padding"
maskUnit8Width = 8
maskUnit32Width = 32
KmsHandlerName = "KmsHandler"
)
var (
DataIdParamCheckError = fmt.Errorf("dataId prefix should start with: %s", CipherPrefix)
ContentParamCheckError = fmt.Errorf("content need to encrypt is nil")
KeyIdParamCheckError = fmt.Errorf("keyId is nil, need to be set")
)
var (
PluginNotFoundError = fmt.Errorf("cannot find encryption plugin by dataId prefix")
)
var (
EmptyEncryptedDataKeyError = fmt.Errorf("empty encrypted data key error")
EmptyPlainDataKeyError = fmt.Errorf("empty plain data key error")
EmptyContentError = fmt.Errorf("encrypt empty content error")
)
var (
EmptyRegionKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty region")
EmptyAkKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty ak")
EmptySkKmsV1ClientInitError = fmt.Errorf("init kmsV1 client failed with empty sk")
EmptyEndpointKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty endpoint")
EmptyPasswordKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty password")
EmptyClientKeyContentKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty client key content")
EmptyCaVerifyKmsV3ClientInitError = fmt.Errorf("init kmsV3 client failed with empty ca verify")
EmptyEndpointKmsRamClientInitError = fmt.Errorf("init kmsRam client failed with empty endpoint")
)

View File

@ -0,0 +1,224 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encryption
import (
"fmt"
"github.com/alibabacloud-go/tea/tea"
dkms_api "github.com/aliyun/alibabacloud-dkms-gcs-go-sdk/openapi"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/pkg/errors"
"strings"
)
type HandlerParam struct {
DataId string `json:"dataId"` //required
Content string `json:"content"` //required
EncryptedDataKey string `json:"encryptedDataKey"`
PlainDataKey string `json:"plainDataKey"`
KeyId string `json:"keyId"`
}
type Plugin interface {
Encrypt(*HandlerParam) error
Decrypt(*HandlerParam) error
AlgorithmName() string
GenerateSecretKey(*HandlerParam) (string, error)
EncryptSecretKey(*HandlerParam) (string, error)
DecryptSecretKey(*HandlerParam) (string, error)
}
type Handler interface {
EncryptionHandler(*HandlerParam) error
DecryptionHandler(*HandlerParam) error
RegisterPlugin(Plugin) error
GetHandlerName() string
}
func NewKmsHandler() Handler {
return newKmsHandler()
}
func newKmsHandler() *KmsHandler {
kmsHandler := &KmsHandler{
encryptionPlugins: make(map[string]Plugin, 2),
}
logger.Debug("successfully create encryption KmsHandler")
return kmsHandler
}
func RegisterConfigEncryptionKmsPlugins(encryptionHandler Handler, clientConfig constant.ClientConfig) {
innerKmsClient, err := innerNewKmsClient(clientConfig)
if innerKmsClient == nil {
err = errors.New("create kms client failed.")
}
if err != nil && innerKmsClient == nil {
err = errors.New("create kms client failed.")
}
if err != nil {
logger.Error(err)
}
if err := encryptionHandler.RegisterPlugin(&KmsAes128Plugin{kmsPlugin{kmsClient: innerKmsClient}}); err != nil {
logger.Errorf("failed to register encryption plugin[%s] to %s", KmsAes128AlgorithmName, encryptionHandler.GetHandlerName())
} else {
logger.Debugf("successfully register encryption plugin[%s] to %s", KmsAes128AlgorithmName, encryptionHandler.GetHandlerName())
}
if err := encryptionHandler.RegisterPlugin(&KmsAes256Plugin{kmsPlugin{kmsClient: innerKmsClient}}); err != nil {
logger.Errorf("failed to register encryption plugin[%s] to %s", KmsAes256AlgorithmName, encryptionHandler.GetHandlerName())
} else {
logger.Debugf("successfully register encryption plugin[%s] to %s", KmsAes256AlgorithmName, encryptionHandler.GetHandlerName())
}
if err := encryptionHandler.RegisterPlugin(&KmsBasePlugin{kmsPlugin{kmsClient: innerKmsClient}}); err != nil {
logger.Errorf("failed to register encryption plugin[%s] to %s", KmsAlgorithmName, encryptionHandler.GetHandlerName())
} else {
logger.Debugf("successfully register encryption plugin[%s] to %s", KmsAlgorithmName, encryptionHandler.GetHandlerName())
}
}
type KmsHandler struct {
encryptionPlugins map[string]Plugin
}
func (d *KmsHandler) EncryptionHandler(param *HandlerParam) error {
if err := d.encryptionParamCheck(*param); err != nil {
return err
}
plugin, err := d.getPluginByDataIdPrefix(param.DataId)
if err != nil {
return err
}
plainSecretKey, err := plugin.GenerateSecretKey(param)
if err != nil {
return err
}
param.PlainDataKey = plainSecretKey
return plugin.Encrypt(param)
}
func (d *KmsHandler) DecryptionHandler(param *HandlerParam) error {
if err := d.decryptionParamCheck(*param); err != nil {
return err
}
plugin, err := d.getPluginByDataIdPrefix(param.DataId)
if err != nil {
return err
}
plainSecretkey, err := plugin.DecryptSecretKey(param)
if err != nil {
return err
}
param.PlainDataKey = plainSecretkey
return plugin.Decrypt(param)
}
func (d *KmsHandler) getPluginByDataIdPrefix(dataId string) (Plugin, error) {
var (
matchedCount int
matchedPlugin Plugin
)
for k, v := range d.encryptionPlugins {
if strings.Contains(dataId, k) {
if len(k) > matchedCount {
matchedCount = len(k)
matchedPlugin = v
}
}
}
if matchedPlugin == nil {
return matchedPlugin, PluginNotFoundError
}
return matchedPlugin, nil
}
func (d *KmsHandler) RegisterPlugin(plugin Plugin) error {
if _, v := d.encryptionPlugins[plugin.AlgorithmName()]; v {
logger.Warnf("encryption algorithm [%s] has already registered to defaultHandler, will be update", plugin.AlgorithmName())
} else {
logger.Debugf("register encryption algorithm [%s] to defaultHandler", plugin.AlgorithmName())
}
d.encryptionPlugins[plugin.AlgorithmName()] = plugin
return nil
}
func (d *KmsHandler) GetHandlerName() string {
return KmsHandlerName
}
func (d *KmsHandler) encryptionParamCheck(param HandlerParam) error {
if err := d.dataIdParamCheck(param.DataId); err != nil {
return DataIdParamCheckError
}
if err := d.contentParamCheck(param.Content); err != nil {
return ContentParamCheckError
}
return nil
}
func (d *KmsHandler) decryptionParamCheck(param HandlerParam) error {
return d.encryptionParamCheck(param)
}
func (d *KmsHandler) keyIdParamCheck(keyId string) error {
if len(keyId) == 0 {
return fmt.Errorf("cipher dataId using kmsService need to set keyId, but keyId is nil")
}
return nil
}
func (d *KmsHandler) dataIdParamCheck(dataId string) error {
if !strings.Contains(dataId, CipherPrefix) {
return fmt.Errorf("dataId prefix should start with: %s", CipherPrefix)
}
return nil
}
func (d *KmsHandler) contentParamCheck(content string) error {
if len(content) == 0 {
return fmt.Errorf("content need to encrypt is nil")
}
return nil
}
func innerNewKmsClient(clientConfig constant.ClientConfig) (kmsClient KmsClient, err error) {
switch clientConfig.KMSVersion {
case constant.KMSv1, constant.DEFAULT_KMS_VERSION:
kmsClient, err = newKmsRamClient(clientConfig)
case constant.KMSv3:
kmsClient, err = newKmsV3Client(clientConfig)
default:
err = fmt.Errorf("init kms client failed. unknown kms version:%s\n", clientConfig.KMSVersion)
}
return kmsClient, err
}
func newKmsV1Client(clientConfig constant.ClientConfig) (KmsClient, error) {
return NewKmsV1ClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
}
func newKmsV3Client(clientConfig constant.ClientConfig) (KmsClient, error) {
return NewKmsV3ClientWithConfig(&dkms_api.Config{
Protocol: tea.String("https"),
Endpoint: tea.String(clientConfig.KMSv3Config.Endpoint),
ClientKeyContent: tea.String(clientConfig.KMSv3Config.ClientKeyContent),
Password: tea.String(clientConfig.KMSv3Config.Password),
}, clientConfig.KMSv3Config.CaContent)
}
func newKmsRamClient(clientConfig constant.ClientConfig) (KmsClient, error) {
return NewKmsRamClient(clientConfig.KMSConfig, clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
}

View File

@ -0,0 +1,303 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encryption
import (
"fmt"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
kms20160120 "github.com/alibabacloud-go/kms-20160120/v3/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
dkms_api "github.com/aliyun/alibabacloud-dkms-gcs-go-sdk/openapi"
dkms_transfer "github.com/aliyun/alibabacloud-dkms-transfer-go-sdk/sdk"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/pkg/errors"
"net/http"
"strings"
)
type KmsClient interface {
Decrypt(cipherContent string) (string, error)
Encrypt(content string, keyId string) (string, error)
GenerateDataKey(keyId, keySpec string) (string, string, error)
GetKmsVersion() constant.KMSVersion
setKmsVersion(constant.KMSVersion)
}
type TransferKmsClient struct {
*dkms_transfer.KmsTransferClient
kmsVersion constant.KMSVersion
}
func NewKmsV1ClientWithAccessKey(regionId, ak, sk string) (*TransferKmsClient, error) {
var rErr error
if rErr = checkKmsV1InitParam(regionId, ak, sk); rErr != nil {
return nil, rErr
}
kmsClient, err := newKmsV1ClientWithAccessKey(regionId, ak, sk)
if err != nil {
rErr = errors.Wrap(err, "init kms v1 client with ak/sk failed")
} else {
kmsClient.setKmsVersion(constant.KMSv1)
}
return kmsClient, rErr
}
func checkKmsV1InitParam(regionId, ak, sk string) error {
if len(regionId) == 0 {
return EmptyRegionKmsV1ClientInitError
}
if len(ak) == 0 {
return EmptyAkKmsV1ClientInitError
}
if len(sk) == 0 {
return EmptySkKmsV1ClientInitError
}
return nil
}
func checkKmsRamInitParam(endpoint, ak, sk string) error {
if len(endpoint) == 0 {
return EmptyEndpointKmsRamClientInitError
}
if len(ak) == 0 {
return EmptyAkKmsV1ClientInitError
}
if len(sk) == 0 {
return EmptySkKmsV1ClientInitError
}
return nil
}
func NewKmsV3ClientWithConfig(config *dkms_api.Config, caVerify string) (*TransferKmsClient, error) {
var rErr error
if rErr = checkKmsV3InitParam(config, caVerify); rErr != nil {
return nil, rErr
}
kmsClient, err := newKmsV3ClientWithConfig(config)
if err != nil {
rErr = errors.Wrap(err, "init kms v3 client with config failed")
} else {
if len(strings.TrimSpace(caVerify)) != 0 {
logger.Debugf("set kms client Ca with content: %s\n", caVerify[:len(caVerify)/maskUnit32Width])
kmsClient.SetVerify(caVerify)
} else {
kmsClient.SetHTTPSInsecure(true)
}
kmsClient.setKmsVersion(constant.KMSv3)
}
return kmsClient, rErr
}
func checkKmsV3InitParam(config *dkms_api.Config, caVerify string) error {
if len(*config.Endpoint) == 0 {
return EmptyEndpointKmsV3ClientInitError
}
if len(*config.Password) == 0 {
return EmptyPasswordKmsV3ClientInitError
}
if len(*config.ClientKeyContent) == 0 {
return EmptyClientKeyContentKmsV3ClientInitError
}
if len(caVerify) == 0 {
return EmptyCaVerifyKmsV3ClientInitError
}
return nil
}
func newKmsV1ClientWithAccessKey(regionId, ak, sk string) (*TransferKmsClient, error) {
logger.Debugf("init kms client with region:[%s], ak:[%s]xxx, sk:[%s]xxx\n",
regionId, ak[:len(ak)/maskUnit8Width], sk[:len(sk)/maskUnit8Width])
return newKmsClient(regionId, ak, sk, nil)
}
func newKmsV3ClientWithConfig(config *dkms_api.Config) (*TransferKmsClient, error) {
logger.Debugf("init kms client with endpoint:[%s], clientKeyContent:[%s], password:[%s]\n",
config.Endpoint, (*config.ClientKeyContent)[:len(*config.ClientKeyContent)/maskUnit8Width],
(*config.Password)[:len(*config.Password)/maskUnit8Width])
return newKmsClient("", "", "", config)
}
func newKmsClient(regionId, ak, sk string, config *dkms_api.Config) (*TransferKmsClient, error) {
client, err := dkms_transfer.NewClientWithAccessKey(regionId, ak, sk, config)
if err != nil {
return nil, err
}
return &TransferKmsClient{
KmsTransferClient: client,
}, nil
}
func (kmsClient *TransferKmsClient) GetKmsVersion() constant.KMSVersion {
return kmsClient.kmsVersion
}
func (kmsClient *TransferKmsClient) setKmsVersion(kmsVersion constant.KMSVersion) {
logger.Debug("successfully set kms client version to " + kmsVersion)
kmsClient.kmsVersion = kmsVersion
}
func (kmsClient *TransferKmsClient) GenerateDataKey(keyId, keySpec string) (string, string, error) {
generateDataKeyRequest := kms.CreateGenerateDataKeyRequest()
generateDataKeyRequest.Scheme = kmsScheme
generateDataKeyRequest.AcceptFormat = kmsAcceptFormat
generateDataKeyRequest.KeyId = keyId
generateDataKeyRequest.KeySpec = keySpec
generateDataKeyResponse, err := kmsClient.KmsTransferClient.GenerateDataKey(generateDataKeyRequest)
if err != nil {
return "", "", err
}
return generateDataKeyResponse.Plaintext, generateDataKeyResponse.CiphertextBlob, nil
}
func (kmsClient *TransferKmsClient) Decrypt(cipherContent string) (string, error) {
request := kms.CreateDecryptRequest()
request.Method = http.MethodPost
request.Scheme = kmsScheme
request.AcceptFormat = kmsAcceptFormat
request.CiphertextBlob = cipherContent
response, err := kmsClient.KmsTransferClient.Decrypt(request)
if err != nil {
return "", fmt.Errorf("kms decrypt failed: %v", err)
}
return response.Plaintext, nil
}
func (kmsClient *TransferKmsClient) Encrypt(content, keyId string) (string, error) {
request := kms.CreateEncryptRequest()
request.Method = http.MethodPost
request.Scheme = kmsScheme
request.AcceptFormat = kmsAcceptFormat
request.Plaintext = content
request.KeyId = keyId
response, err := kmsClient.KmsTransferClient.Encrypt(request)
if err != nil {
return "", fmt.Errorf("kms encrypt failed: %v", err)
}
return response.CiphertextBlob, nil
}
func GetDefaultKMSv1KeyId() string {
return constant.MSE_KMSv1_DEFAULT_KEY_ID
}
type RamKmsClient struct {
*kms20160120.Client
kmsVersion constant.KMSVersion
runtime *util.RuntimeOptions
}
func NewKmsRamClient(kmsConfig *constant.KMSConfig, regionId, ak, sk string) (*RamKmsClient, error) {
if kmsConfig == nil || len(kmsConfig.Endpoint) == 0 {
if err := checkKmsV1InitParam(regionId, ak, sk); err != nil {
return nil, err
}
KmsV1Config := &openapi.Config{}
KmsV1Config.AccessKeyId = tea.String(ak)
KmsV1Config.AccessKeySecret = tea.String(sk)
KmsV1Config.RegionId = tea.String(regionId)
_result, _err := kms20160120.NewClient(KmsV1Config)
if _err != nil {
return nil, _err
}
_ramClient := &RamKmsClient{
Client: _result,
kmsVersion: constant.KMSv1,
runtime: &util.RuntimeOptions{},
}
return _ramClient, nil
}
if err := checkKmsRamInitParam(kmsConfig.Endpoint, ak, sk); err != nil {
return nil, err
}
config := &openapi.Config{}
config.AccessKeyId = tea.String(ak)
config.AccessKeySecret = tea.String(sk)
if len(regionId) != 0 {
config.RegionId = tea.String(regionId)
}
config.Endpoint = tea.String(kmsConfig.Endpoint)
config.Ca = tea.String(kmsConfig.CaContent)
runtimeOption := &util.RuntimeOptions{}
if len(kmsConfig.CaContent) == 0 {
runtimeOption.IgnoreSSL = tea.Bool(true)
}
if kmsConfig.OpenSSL == "true" {
runtimeOption.IgnoreSSL = tea.Bool(false)
} else if kmsConfig.OpenSSL == "false" {
runtimeOption.IgnoreSSL = tea.Bool(true)
}
_result, _err := kms20160120.NewClient(config)
if _err != nil {
return nil, _err
}
_ramClient := &RamKmsClient{
Client: _result,
kmsVersion: constant.KMSv3,
runtime: runtimeOption,
}
return _ramClient, nil
}
func (kmsClient *RamKmsClient) GetKmsVersion() constant.KMSVersion {
return kmsClient.kmsVersion
}
func (kmsClient *RamKmsClient) setKmsVersion(kmsVersion constant.KMSVersion) {
logger.Debug("successfully set kms client version to " + kmsVersion)
kmsClient.kmsVersion = kmsVersion
}
func (kmsClient *RamKmsClient) GenerateDataKey(keyId, keySpec string) (string, string, error) {
request := &kms20160120.GenerateDataKeyRequest{
KeyId: tea.String(keyId),
KeySpec: tea.String(keySpec),
}
_body, _err := kmsClient.Client.GenerateDataKeyWithOptions(request, kmsClient.runtime)
if _err != nil {
return "", "", _err
}
return *_body.Body.Plaintext, *_body.Body.CiphertextBlob, nil
}
func (kmsClient *RamKmsClient) Decrypt(cipherContent string) (string, error) {
request := &kms20160120.DecryptRequest{
CiphertextBlob: tea.String(cipherContent),
}
_body, _err := kmsClient.Client.DecryptWithOptions(request, kmsClient.runtime)
if _err != nil {
return "", _err
}
return *_body.Body.Plaintext, nil
}
func (kmsClient *RamKmsClient) Encrypt(content, keyId string) (string, error) {
request := &kms20160120.EncryptRequest{
Plaintext: tea.String(content),
KeyId: tea.String(keyId),
}
_body, _err := kmsClient.Client.EncryptWithOptions(request, kmsClient.runtime)
if _err != nil {
return "", _err
}
return *_body.Body.CiphertextBlob, nil
}

View File

@ -0,0 +1,299 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package encryption
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
inner_encoding "github.com/nacos-group/nacos-sdk-go/v2/common/encoding"
"strings"
)
type kmsPlugin struct {
kmsClient KmsClient
}
func (k *kmsPlugin) Encrypt(param *HandlerParam) error {
err := k.encryptionParamCheck(*param)
if err != nil {
return err
}
secretKeyBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.PlainDataKey))
if err != nil {
return err
}
contentUtf8Bytes := inner_encoding.DecodeString2Utf8Bytes(param.Content)
encryptedContent, err := AesEcbPkcs5PaddingEncrypt(contentUtf8Bytes, secretKeyBase64Decoded)
if err != nil {
return err
}
contentBase64Encoded, err := inner_encoding.EncodeBase64(encryptedContent)
if err != nil {
return err
}
param.Content = inner_encoding.EncodeUtf8Bytes2String(contentBase64Encoded)
return nil
}
func (k *kmsPlugin) Decrypt(param *HandlerParam) error {
err := k.decryptionParamCheck(*param)
if err != nil {
return err
}
secretKeyBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.PlainDataKey))
if err != nil {
return err
}
contentBase64Decoded, err := inner_encoding.DecodeBase64(inner_encoding.DecodeString2Utf8Bytes(param.Content))
if err != nil {
return err
}
decryptedContent, err := AesEcbPkcs5PaddingDecrypt(contentBase64Decoded, secretKeyBase64Decoded)
if err != nil {
return err
}
param.Content = inner_encoding.EncodeUtf8Bytes2String(decryptedContent)
return nil
}
func (k *kmsPlugin) AlgorithmName() string {
return ""
}
func (k *kmsPlugin) GenerateSecretKey(param *HandlerParam) (string, error) {
return "", nil
}
func (k *kmsPlugin) EncryptSecretKey(param *HandlerParam) (string, error) {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return "", err
}
if len(param.PlainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
encryptedDataKey, err := k.kmsClient.Encrypt(param.PlainDataKey, keyId)
if err != nil {
return "", err
}
if len(encryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
param.EncryptedDataKey = encryptedDataKey
return encryptedDataKey, nil
}
func (k *kmsPlugin) DecryptSecretKey(param *HandlerParam) (string, error) {
if len(param.EncryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
plainDataKey, err := k.kmsClient.Decrypt(param.EncryptedDataKey)
if err != nil {
return "", err
}
if len(plainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
param.PlainDataKey = plainDataKey
return plainDataKey, nil
}
func (k *kmsPlugin) encryptionParamCheck(param HandlerParam) error {
if err := k.plainDataKeyParamCheck(param.PlainDataKey); err != nil {
return KeyIdParamCheckError
}
if err := k.contentParamCheck(param.Content); err != nil {
return ContentParamCheckError
}
return nil
}
func (k *kmsPlugin) decryptionParamCheck(param HandlerParam) error {
return k.encryptionParamCheck(param)
}
func (k *kmsPlugin) plainDataKeyParamCheck(plainDataKey string) error {
if len(plainDataKey) == 0 {
return EmptyPlainDataKeyError
}
return nil
}
func (k *kmsPlugin) dataIdParamCheck(dataId string) error {
if !strings.Contains(dataId, CipherPrefix) {
return fmt.Errorf("dataId prefix should start with: %s", CipherPrefix)
}
return nil
}
func (k *kmsPlugin) keyIdParamCheck(keyId string) (string, error) {
if len(strings.TrimSpace(keyId)) == 0 {
if k.kmsClient.GetKmsVersion() == constant.KMSv1 {
return GetDefaultKMSv1KeyId(), nil
}
return "", KeyIdParamCheckError
}
return keyId, nil
}
func (k *kmsPlugin) contentParamCheck(content string) error {
if len(content) == 0 {
return fmt.Errorf("content need to encrypt is nil")
}
return nil
}
type KmsAes128Plugin struct {
kmsPlugin
}
func (k *KmsAes128Plugin) Encrypt(param *HandlerParam) error {
return k.kmsPlugin.Encrypt(param)
}
func (k *KmsAes128Plugin) Decrypt(param *HandlerParam) error {
return k.kmsPlugin.Decrypt(param)
}
func (k *KmsAes128Plugin) AlgorithmName() string {
return KmsAes128AlgorithmName
}
func (k *KmsAes128Plugin) GenerateSecretKey(param *HandlerParam) (string, error) {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return "", err
}
plainSecretKey, encryptedSecretKey, err := k.kmsClient.GenerateDataKey(keyId, kmsAes128KeySpec)
if err != nil {
return "", err
}
param.PlainDataKey = plainSecretKey
param.EncryptedDataKey = encryptedSecretKey
if len(param.PlainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
if len(param.EncryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
return plainSecretKey, nil
}
func (k *KmsAes128Plugin) EncryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.EncryptSecretKey(param)
}
func (k *KmsAes128Plugin) DecryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.DecryptSecretKey(param)
}
type KmsAes256Plugin struct {
kmsPlugin
}
func (k *KmsAes256Plugin) Encrypt(param *HandlerParam) error {
return k.kmsPlugin.Encrypt(param)
}
func (k *KmsAes256Plugin) Decrypt(param *HandlerParam) error {
return k.kmsPlugin.Decrypt(param)
}
func (k *KmsAes256Plugin) AlgorithmName() string {
return KmsAes256AlgorithmName
}
func (k *KmsAes256Plugin) GenerateSecretKey(param *HandlerParam) (string, error) {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return "", err
}
plainSecretKey, encryptedSecretKey, err := k.kmsClient.GenerateDataKey(keyId, kmsAes256KeySpec)
if err != nil {
return "", err
}
param.PlainDataKey = plainSecretKey
param.EncryptedDataKey = encryptedSecretKey
if len(param.PlainDataKey) == 0 {
return "", EmptyPlainDataKeyError
}
if len(param.EncryptedDataKey) == 0 {
return "", EmptyEncryptedDataKeyError
}
return plainSecretKey, nil
}
func (k *KmsAes256Plugin) EncryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.EncryptSecretKey(param)
}
func (k *KmsAes256Plugin) DecryptSecretKey(param *HandlerParam) (string, error) {
return k.kmsPlugin.DecryptSecretKey(param)
}
type KmsBasePlugin struct {
kmsPlugin
}
func (k *KmsBasePlugin) Encrypt(param *HandlerParam) error {
var keyId string
var err error
if keyId, err = k.keyIdParamCheck(param.KeyId); err != nil {
return err
}
if len(param.Content) == 0 {
return EmptyContentError
}
encryptedContent, err := k.kmsClient.Encrypt(param.Content, keyId)
if err != nil {
return err
}
param.Content = encryptedContent
return nil
}
func (k *KmsBasePlugin) Decrypt(param *HandlerParam) error {
if len(param.Content) == 0 {
return nil
}
plainContent, err := k.kmsClient.Decrypt(param.Content)
if err != nil {
return err
}
param.Content = plainContent
return nil
}
func (k *KmsBasePlugin) AlgorithmName() string {
return KmsAlgorithmName
}
func (k *KmsBasePlugin) GenerateSecretKey(param *HandlerParam) (string, error) {
return "", nil
}
func (k *KmsBasePlugin) EncryptSecretKey(param *HandlerParam) (string, error) {
return "", nil
}
func (k *KmsBasePlugin) DecryptSecretKey(param *HandlerParam) (string, error) {
return "", nil
}

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

@ -0,0 +1,89 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package file
import (
"log"
"os"
"path/filepath"
"runtime"
"strings"
)
var osType string
var path string
const WINDOWS = "windows"
func init() {
osType = runtime.GOOS
if os.IsPathSeparator('\\') { //前边的判断是否是系统的分隔符
path = "\\"
} else {
path = "/"
}
}
func MkdirIfNecessary(createDir string) (err error) {
s := strings.Split(createDir, path)
startIndex := 0
dir := ""
if s[0] == "" {
startIndex = 1
} else {
dir, _ = os.Getwd() //当前的目录
}
for i := startIndex; i < len(s); i++ {
var d string
if osType == WINDOWS && filepath.IsAbs(createDir) {
d = strings.Join(s[startIndex:i+1], path)
} else {
d = dir + path + strings.Join(s[startIndex:i+1], path)
}
if _, e := os.Stat(d); os.IsNotExist(e) {
err = os.Mkdir(d, os.ModePerm) //在当前目录下生成md目录
if err != nil {
break
}
}
}
return err
}
func GetCurrentPath() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Println("can not get current path")
}
return dir
}
func IsExistFile(filePath string) bool {
if len(filePath) == 0 {
return false
}
_, err := os.Stat(filePath)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}

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

@ -0,0 +1,30 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package file
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMkdirIfNecessaryForAbsPath(t *testing.T) {
path := GetCurrentPath() + string(os.PathSeparator) + "log"
err := MkdirIfNecessary(path)
assert.Nil(t, err)
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package filter
import (
nacos_inner_encryption "github.com/nacos-group/nacos-sdk-go/v2/common/encryption"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/pkg/errors"
"strings"
)
const (
defaultConfigEncryptionFilterName = "defaultConfigEncryptionFilter"
)
var (
noNeedEncryptionError = errors.New("dataId doesn't need to encrypt/decrypt.")
)
type DefaultConfigEncryptionFilter struct {
handler nacos_inner_encryption.Handler
}
func NewDefaultConfigEncryptionFilter(handler nacos_inner_encryption.Handler) IConfigFilter {
return &DefaultConfigEncryptionFilter{handler}
}
func (d *DefaultConfigEncryptionFilter) DoFilter(param *vo.ConfigParam) error {
if err := d.paramCheck(*param); err != nil {
if errors.Is(err, noNeedEncryptionError) {
return nil
}
}
if param.UsageType == vo.RequestType {
encryptionParam := &nacos_inner_encryption.HandlerParam{
DataId: param.DataId,
Content: param.Content,
KeyId: param.KmsKeyId,
}
if err := d.handler.EncryptionHandler(encryptionParam); err != nil {
return err
}
param.Content = encryptionParam.Content
param.EncryptedDataKey = encryptionParam.EncryptedDataKey
} else if param.UsageType == vo.ResponseType {
decryptionParam := &nacos_inner_encryption.HandlerParam{
DataId: param.DataId,
Content: param.Content,
EncryptedDataKey: param.EncryptedDataKey,
}
if err := d.handler.DecryptionHandler(decryptionParam); err != nil {
return err
}
param.Content = decryptionParam.Content
}
return nil
}
func (d *DefaultConfigEncryptionFilter) GetOrder() int {
return 0
}
func (d *DefaultConfigEncryptionFilter) GetFilterName() string {
return defaultConfigEncryptionFilterName
}
func (d *DefaultConfigEncryptionFilter) paramCheck(param vo.ConfigParam) error {
if !strings.HasPrefix(param.DataId, nacos_inner_encryption.CipherPrefix) ||
len(strings.TrimSpace(param.Content)) == 0 {
return noNeedEncryptionError
}
return nil
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package filter
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
type IConfigFilterChain interface {
AddFilter(IConfigFilter) error
GetFilters() []IConfigFilter
DoFilters(*vo.ConfigParam) error
DoFilterByName(*vo.ConfigParam, string) error
}
type IConfigFilter interface {
DoFilter(*vo.ConfigParam) error
GetOrder() int
GetFilterName() string
}
func RegisterConfigFilterToChain(chain IConfigFilterChain, filter IConfigFilter) error {
return chain.AddFilter(filter)
}
func NewConfigFilterChainManager() IConfigFilterChain {
return newConfigFilterChainManager()
}
func newConfigFilterChainManager() *DefaultConfigFilterChainManager {
return &DefaultConfigFilterChainManager{
configFilterPriorityQueue: make([]IConfigFilter, 0, 2),
}
}
type DefaultConfigFilterChainManager struct {
configFilterPriorityQueue
}
func (m *DefaultConfigFilterChainManager) AddFilter(filter IConfigFilter) error {
return m.configFilterPriorityQueue.addFilter(filter)
}
func (m *DefaultConfigFilterChainManager) GetFilters() []IConfigFilter {
return m.configFilterPriorityQueue
}
func (m *DefaultConfigFilterChainManager) DoFilters(param *vo.ConfigParam) error {
for index := 0; index < len(m.GetFilters()); index++ {
if err := m.GetFilters()[index].DoFilter(param); err != nil {
return err
}
}
return nil
}
func (m *DefaultConfigFilterChainManager) DoFilterByName(param *vo.ConfigParam, name string) error {
for index := 0; index < len(m.GetFilters()); index++ {
if m.GetFilters()[index].GetFilterName() == name {
if err := m.GetFilters()[index].DoFilter(param); err != nil {
return err
}
return nil
}
}
return fmt.Errorf("cannot find the filter[%s]", name)
}
type configFilterPriorityQueue []IConfigFilter
func (c *configFilterPriorityQueue) addFilter(filter IConfigFilter) error {
var pos int = len(*c)
for i := 0; i < len(*c); i++ {
if filter.GetFilterName() == (*c)[i].GetFilterName() {
return nil
}
if filter.GetOrder() < (*c)[i].GetOrder() {
pos = i
break
}
}
if pos == len(*c) {
*c = append((*c)[:], filter)
} else {
temp := append((*c)[:pos], filter)
*c = append(temp[:], (*c)[pos:]...)
}
return nil
}

View File

@ -1,31 +1,40 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http_agent
import (
"net/http"
"net/url"
"strings"
"time"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-08 14:08
**/
func delete(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.HasSuffix(path, "?") {
path = path + "?"
func delete(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if len(params) > 0 {
if !strings.HasSuffix(path, "?") {
path = path + "?"
}
for key, value := range params {
path = path + key + "=" + url.QueryEscape(value) + "&"
}
if strings.HasSuffix(path, "&") {
path = path[:len(path)-1]
}
}
for key, value := range params {
path = path + key + "=" + value + "&"
}
if strings.HasSuffix(path, "&") {
path = path[:len(path)-1]
}
client := http.Client{}
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
request, errNew := http.NewRequest(http.MethodDelete, path, nil)
if errNew != nil {

View File

@ -1,3 +1,19 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http_agent
import (
@ -7,14 +23,6 @@ import (
"strconv"
)
/**
*
* @description : mock http response
*
* @author : codezhang
*
* @create : 2019-01-11 12:10
**/
type fakeHttpResponseBody struct {
body io.ReadSeeker
}

View File

@ -1,31 +1,43 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http_agent
import (
"net/http"
"net/url"
"strings"
"time"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-07 15:13
**/
func get(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.HasSuffix(path, "?") {
func get(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.Contains(path, "?") {
path = path + "?"
}
for key, value := range params {
path = path + key + "=" + value + "&"
if !strings.HasSuffix(path, "&") {
path = path + "&"
}
path = path + key + "=" + url.QueryEscape(value) + "&"
}
if strings.HasSuffix(path, "&") {
path = path[:len(path)-1]
}
client := http.Client{}
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
request, errNew := http.NewRequest(http.MethodGet, path, nil)
if errNew != nil {
@ -34,6 +46,7 @@ func get(path string, header http.Header, timeoutMs uint64, params map[string]st
}
request.Header = header
resp, errDo := client.Do(request)
if errDo != nil {
err = errDo
} else {

View File

@ -1,27 +1,44 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http_agent
import (
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/utils"
"io/ioutil"
"log"
"io"
"net/http"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/tls"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-10 11:26
**/
type HttpAgent struct {
TlsConfig constant.TLSConfig
}
func (agent *HttpAgent) Get(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return get(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return get(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) RequestOnlyResult(method string, path string, header http.Header, timeoutMs uint64, params map[string]string) string {
@ -41,20 +58,20 @@ func (agent *HttpAgent) RequestOnlyResult(method string, path string, header htt
response, err = agent.Delete(path, header, timeoutMs, params)
break
default:
log.Printf("[ERROR]:request method[%s], path[%s],header:[%s],params:[%s], not avaliable method ", method, path, utils.ToJsonString(header), utils.ToJsonString(params))
logger.Errorf("request method[%s], path[%s],header:[%s],params:[%s], not avaliable method ", method, path, util.ToJsonString(header), util.ToJsonString(params))
}
if err != nil {
log.Printf("[ERROR]:request method[%s],request path[%s],header:[%s],params:[%s],err:%s", method, path, utils.ToJsonString(header), utils.ToJsonString(params), err.Error())
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],err:%+v", method, path, util.ToJsonString(header), util.ToJsonString(params), err)
return ""
}
if response.StatusCode != 200 {
log.Printf("[ERROR]:request method[%s],request path[%s],header:[%s],params:[%s],status code error:%d", method, path, utils.ToJsonString(header), utils.ToJsonString(params), response.StatusCode)
if response.StatusCode != constant.RESPONSE_CODE_SUCCESS {
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],status code error:%d", method, path, util.ToJsonString(header), util.ToJsonString(params), response.StatusCode)
return ""
}
bytes, errRead := ioutil.ReadAll(response.Body)
bytes, errRead := io.ReadAll(response.Body)
defer response.Body.Close()
if errRead != nil {
log.Printf("[ERROR]:request method[%s],request path[%s],header:[%s],params:[%s],read error:%s", method, path, utils.ToJsonString(header), utils.ToJsonString(params), errRead.Error())
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],read error:%+v", method, path, util.ToJsonString(header), util.ToJsonString(params), errRead)
return ""
}
return string(bytes)
@ -76,20 +93,44 @@ func (agent *HttpAgent) Request(method string, path string, header http.Header,
response, err = agent.Delete(path, header, timeoutMs, params)
return
default:
err = errors.New("not avaliable method")
log.Printf("[ERROR]:request method[%s], path[%s],header:[%s],params:[%s], not avaliable method ", method, path, utils.ToJsonString(header), utils.ToJsonString(params))
err = errors.New("not available method")
logger.Errorf("request method[%s], path[%s],header:[%s],params:[%s], not available method ", method, path, util.ToJsonString(header), util.ToJsonString(params))
}
return
}
func (agent *HttpAgent) Post(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return post(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return post(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) Delete(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return delete(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return delete(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) Put(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return put(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return put(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) createClient() (*http.Client, error) {
if !agent.TlsConfig.Enable {
return &http.Client{}, nil
}
cfg, err := tls.NewTLS(agent.TlsConfig)
if err != nil {
return nil, err
}
return &http.Client{Transport: &http.Transport{TLSClientConfig: cfg}}, nil
}

View File

@ -1,16 +1,23 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http_agent
import "net/http"
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-10 11:07
**/
//go:generate mockgen -destination ../../mock/mock_http_agent_interface.go -package mock -source=./http_agent_interface.go
type IHttpAgent interface {

View File

@ -1,32 +1,32 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http_agent
import (
"net/http"
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-07 15:13
**/
func post(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client := http.Client{}
func post(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
var body string
for key, value := range params {
if len(value) > 0 {
body += key + "=" + value + "&"
}
}
if strings.HasSuffix(body, "&") {
body = body[:len(body)-1]
}
body := util.GetUrlFormedMap(params)
request, errNew := http.NewRequest(http.MethodPost, path, strings.NewReader(body))
if errNew != nil {
err = errNew

View File

@ -1,23 +1,28 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package http_agent
import (
"log"
"net/http"
"strings"
"time"
)
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-09 11:24
**/
func put(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client := http.Client{}
func put(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
var body string
for key, value := range params {
@ -36,7 +41,6 @@ func put(path string, header http.Header, timeoutMs uint64, params map[string]st
request.Header = header
resp, errDo := client.Do(request)
if errDo != nil {
log.Println(errDo)
err = errDo
} else {
response = resp

View File

@ -1,25 +1,182 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package logger
import (
"github.com/lestrrat/go-file-rotatelogs"
"github.com/nacos-group/nacos-sdk-go/common/util"
"log"
"os"
"path/filepath"
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
func InitLog(logDir string) error {
err := util.MkdirIfNecessary(logDir)
if err != nil {
return err
}
logDir = logDir + string(os.PathSeparator)
rl, err := rotatelogs.New(filepath.Join(logDir, "nacos-sdk.log-%Y%m%d%H%M"), rotatelogs.WithRotationTime(time.Hour), rotatelogs.WithMaxAge(48*time.Hour), rotatelogs.WithLinkName(filepath.Join(logDir, "nacos-sdk.log")))
if err != nil {
return err
}
log.SetOutput(rl)
log.SetFlags(log.LstdFlags)
return nil
var (
logger Logger
logLock sync.RWMutex
)
var levelMap = map[string]zapcore.Level{
"debug": zapcore.DebugLevel,
"info": zapcore.InfoLevel,
"warn": zapcore.WarnLevel,
"error": zapcore.ErrorLevel,
}
type Config struct {
Level string
Sampling *SamplingConfig
AppendToStdout bool
LogRollingConfig *lumberjack.Logger
}
type SamplingConfig struct {
Initial int
Thereafter int
Tick time.Duration
}
type NacosLogger struct {
Logger
}
// Logger is the interface for Logger types
type Logger interface {
Info(args ...interface{})
Warn(args ...interface{})
Error(args ...interface{})
Debug(args ...interface{})
Infof(fmt string, args ...interface{})
Warnf(fmt string, args ...interface{})
Errorf(fmt string, args ...interface{})
Debugf(fmt string, args ...interface{})
}
func init() {
zapLoggerConfig := zap.NewDevelopmentConfig()
zapLoggerEncoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
zapLoggerConfig.EncoderConfig = zapLoggerEncoderConfig
zapLogger, _ := zapLoggerConfig.Build(zap.AddCaller(), zap.AddCallerSkip(1))
SetLogger(&NacosLogger{zapLogger.Sugar()})
}
func BuildLoggerConfig(clientConfig constant.ClientConfig) Config {
loggerConfig := Config{
Level: clientConfig.LogLevel,
AppendToStdout: clientConfig.AppendToStdout,
}
if clientConfig.LogSampling != nil {
loggerConfig.Sampling = &SamplingConfig{
Initial: clientConfig.LogSampling.Initial,
Thereafter: clientConfig.LogSampling.Thereafter,
Tick: clientConfig.LogSampling.Tick,
}
}
loggerConfig.LogRollingConfig = &lumberjack.Logger{
Filename: clientConfig.LogDir + string(os.PathSeparator) + constant.LOG_FILE_NAME,
}
logRollingConfig := clientConfig.LogRollingConfig
if logRollingConfig != nil {
loggerConfig.LogRollingConfig.MaxSize = logRollingConfig.MaxSize
loggerConfig.LogRollingConfig.MaxAge = logRollingConfig.MaxAge
loggerConfig.LogRollingConfig.MaxBackups = logRollingConfig.MaxBackups
loggerConfig.LogRollingConfig.LocalTime = logRollingConfig.LocalTime
loggerConfig.LogRollingConfig.Compress = logRollingConfig.Compress
} else {
loggerConfig.LogRollingConfig.MaxSize = 100
loggerConfig.LogRollingConfig.MaxAge = 30
loggerConfig.LogRollingConfig.MaxBackups = 5
loggerConfig.LogRollingConfig.LocalTime = true
loggerConfig.LogRollingConfig.Compress = false
}
return loggerConfig
}
// InitLogger is init global logger for nacos
func InitLogger(config Config) (err error) {
logLock.Lock()
defer logLock.Unlock()
logger, err = InitNacosLogger(config)
return
}
// InitNacosLogger is init nacos default logger
func InitNacosLogger(config Config) (Logger, error) {
logLevel := getLogLevel(config.Level)
encoder := getEncoder()
writer := config.getLogWriter()
if config.AppendToStdout {
writer = zapcore.NewMultiWriteSyncer(writer, zapcore.AddSync(os.Stdout))
}
core := zapcore.NewCore(zapcore.NewConsoleEncoder(encoder), writer, logLevel)
zaplogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
return &NacosLogger{zaplogger.Sugar()}, nil
}
func getLogLevel(level string) zapcore.Level {
if zapLevel, ok := levelMap[level]; ok {
return zapLevel
}
return zapcore.InfoLevel
}
func getEncoder() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
// SetLogger sets logger for sdk
func SetLogger(log Logger) {
logLock.Lock()
defer logLock.Unlock()
logger = log
}
func GetLogger() Logger {
logLock.RLock()
defer logLock.RUnlock()
return logger
}
// getLogWriter get Lumberjack writer by LumberjackConfig
func (c *Config) getLogWriter() zapcore.WriteSyncer {
return zapcore.AddSync(c.LogRollingConfig)
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package logger
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func reset() {
SetLogger(nil)
}
func TestInitLogger(t *testing.T) {
config := Config{
Level: "degug",
}
err := InitLogger(config)
assert.NoError(t, err)
reset()
}
func TestGetLogger(t *testing.T) {
// not yet init get default log
log := GetLogger()
config := Config{
Level: "debug",
}
_ = InitLogger(config)
// after init logger
log2 := GetLogger()
assert.NotEqual(t, log, log2)
reset()
}
func TestSetLogger(t *testing.T) {
// not yet init get default log
log := GetLogger()
log1 := &mockLogger{}
SetLogger(log1)
// after set logger
log2 := GetLogger()
assert.NotEqual(t, log, log2)
assert.Equal(t, log1, log2)
reset()
}
func TestRaceLogger(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(3)
go func() {
defer wg.Done()
SetLogger(&mockLogger{})
}()
go func() {
defer wg.Done()
_ = GetLogger()
}()
go func() {
defer wg.Done()
config := Config{
Level: "degug",
}
_ = InitLogger(config)
}()
}
wg.Wait()
reset()
}
type mockLogger struct {
}
func (m mockLogger) Info(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Warn(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Error(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Debug(args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Infof(fmt string, args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Warnf(fmt string, args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Errorf(fmt string, args ...interface{}) {
panic("implement me")
}
func (m mockLogger) Debugf(fmt string, args ...interface{}) {
panic("implement me")
}

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

@ -0,0 +1,57 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package logger
// Info is info level
func Info(args ...interface{}) {
GetLogger().Info(args...)
}
// Warn is warning level
func Warn(args ...interface{}) {
GetLogger().Warn(args...)
}
// Error is error level
func Error(args ...interface{}) {
GetLogger().Error(args...)
}
// Debug is debug level
func Debug(args ...interface{}) {
GetLogger().Debug(args...)
}
// Infof is format info level
func Infof(fmt string, args ...interface{}) {
GetLogger().Infof(fmt, args...)
}
// Warnf is format warning level
func Warnf(fmt string, args ...interface{}) {
GetLogger().Warnf(fmt, args...)
}
// Errorf is format error level
func Errorf(fmt string, args ...interface{}) {
GetLogger().Errorf(fmt, args...)
}
// Debugf is format debug level
func Debugf(fmt string, args ...interface{}) {
GetLogger().Debugf(fmt, args...)
}

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

@ -0,0 +1,65 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package monitor
import "github.com/prometheus/client_golang/prometheus"
var (
gaugeMonitorVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "nacos_monitor",
Help: "nacos_monitor",
}, []string{"module", "name"})
histogramMonitorVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "nacos_client_request",
Help: "nacos_client_request",
}, []string{"module", "method", "url", "code"})
)
// register collectors vec
func init() {
prometheus.MustRegister(gaugeMonitorVec, histogramMonitorVec)
}
// get gauge with labels and use gaugeMonitorVec
func GetGaugeWithLabels(labels ...string) prometheus.Gauge {
return gaugeMonitorVec.WithLabelValues(labels...)
}
func GetServiceInfoMapSizeMonitor() prometheus.Gauge {
return GetGaugeWithLabels("serviceInfo", "serviceInfoMapSize")
}
func GetDom2BeatSizeMonitor() prometheus.Gauge {
return GetGaugeWithLabels("dom2Beat", "dom2BeatSize")
}
func GetListenConfigCountMonitor() prometheus.Gauge {
return GetGaugeWithLabels("listenConfig", "listenConfigCount")
}
// get histogram with labels and use histogramMonitorVec
func GetHistogramWithLabels(labels ...string) prometheus.Observer {
return histogramMonitorVec.WithLabelValues(labels...)
}
func GetConfigRequestMonitor(method, url, code string) prometheus.Observer {
return GetHistogramWithLabels("config", method, url, code)
}
func GetNamingRequestMonitor(method, url, code string) prometheus.Observer {
return GetHistogramWithLabels("naming", method, url, code)
}

View File

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

View File

@ -1,18 +1,54 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nacos_error
/**
*
* @description :
*
* @author : codezhang
*
* @create : 2019-01-14 11:22
**/
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
type NacosError struct {
ErrMsg string
errorCode string
errMsg string
originError error
}
func NewNacosError(errorCode string, errMsg string, originError error) *NacosError {
return &NacosError{
errorCode: errorCode,
errMsg: errMsg,
originError: originError,
}
}
func (err *NacosError) Error() (str string) {
return err.ErrMsg
nacosErrMsg := fmt.Sprintf("[%s] %s", err.ErrorCode(), err.errMsg)
if err.originError != nil {
return nacosErrMsg + "\ncaused by:\n" + err.originError.Error()
}
return nacosErrMsg
}
func (err *NacosError) ErrorCode() string {
if err.errorCode == "" {
return constant.DefaultClientErrorCode
} else {
return err.errorCode
}
}

View File

@ -1,164 +1,365 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nacos_server
import (
"errors"
"fmt"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/utils"
"github.com/satori/go.uuid"
"io/ioutil"
"log"
"context"
"io"
"math/rand"
"net/http"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type NacosServer struct {
sync.RWMutex
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
endpoint string
lastSrvRefTime int64
vipSrvRefInterMills int64
securityLogin security.SecurityProxy
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
endpoint string
lastSrvRefTime int64
vipSrvRefInterMills int64
contextPath string
endpointContextPath string
endpointQueryParams string
endpointQueryHeader map[string][]string
clusterName string
currentIndex int32
ServerSrcChangeSignal chan struct{}
}
func NewNacosServer(serverList []constant.ServerConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string) (NacosServer, error) {
if len(serverList) == 0 && endpoint == "" {
return NacosServer{}, errors.New("both serverlist and endpoint are empty")
func NewNacosServer(ctx context.Context, serverList []constant.ServerConfig, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string, endpointQueryHeader map[string][]string) (*NacosServer, error) {
return NewNacosServerWithRamCredentialProvider(ctx, serverList, clientCfg, httpAgent, timeoutMs, endpoint, endpointQueryHeader, nil)
}
func NewNacosServerWithRamCredentialProvider(ctx context.Context, serverList []constant.ServerConfig, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string, endpointQueryHeader map[string][]string, provider security.RamCredentialProvider) (*NacosServer, error) {
severLen := len(serverList)
if severLen == 0 && endpoint == "" {
return &NacosServer{}, errors.New("both serverlist and endpoint are empty")
}
securityLogin := security.NewSecurityProxyWithRamCredentialProvider(clientCfg, serverList, httpAgent, provider)
ns := NacosServer{
serverList: serverList,
httpAgent: httpAgent,
timeoutMs: timeoutMs,
endpoint: endpoint,
vipSrvRefInterMills: 10000,
serverList: serverList,
securityLogin: securityLogin,
httpAgent: httpAgent,
timeoutMs: timeoutMs,
endpoint: endpoint,
vipSrvRefInterMills: 10000,
endpointContextPath: clientCfg.EndpointContextPath,
endpointQueryParams: clientCfg.EndpointQueryParams,
endpointQueryHeader: endpointQueryHeader,
clusterName: clientCfg.ClusterName,
contextPath: clientCfg.ContextPath,
ServerSrcChangeSignal: make(chan struct{}, 1),
}
ns.initRefreshSrvIfNeed()
return ns, nil
if severLen > 0 {
ns.currentIndex = rand.Int31n(int32(severLen))
} else {
ns.initRefreshSrvIfNeed(ctx)
}
ns.securityLogin.Login()
ns.securityLogin.AutoRefresh(ctx)
return &ns, nil
}
func (server *NacosServer) callServer(api string, params map[string]string, method string, curServer string, contextPath string) (result string, err error) {
func (server *NacosServer) callConfigServer(api string, params map[string]string, newHeaders map[string]string,
method string, curServer string, contextPath string, timeoutMS uint64) (result string, err error) {
start := time.Now()
if contextPath == "" {
contextPath = constant.WEB_CONTEXT
}
url := "http://" + curServer + contextPath + api
url := curServer + contextPath + api
headers := map[string][]string{}
for k, v := range newHeaders {
if k != "accessKey" && k != "secretKey" {
headers[k] = []string{v}
}
}
headers["Client-Version"] = []string{constant.CLIENT_VERSION}
headers["User-Agent"] = []string{constant.CLIENT_VERSION}
//headers["Accept-Encoding"] = []string{"gzip,deflate,sdch"}
headers["Connection"] = []string{"Keep-Alive"}
headers["exConfigInfo"] = []string{"true"}
uid, err := uuid.NewV4()
if err != nil {
return
}
headers["RequestId"] = []string{uid.String()}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, 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 = io.ReadAll(response.Body)
defer response.Body.Close()
if err != nil {
return
}
result = string(bytes)
if response.StatusCode == constant.RESPONSE_CODE_SUCCESS {
return
} else {
err = nacos_error.NewNacosError(strconv.Itoa(response.StatusCode), string(bytes), nil)
return
}
}
func (server *NacosServer) callServer(api string, params map[string]string, method string, curServer string, contextPath string) (result string, err error) {
start := time.Now()
if contextPath == "" {
contextPath = constant.WEB_CONTEXT
}
url := curServer + contextPath + api
headers := map[string][]string{}
headers["Client-Version"] = []string{constant.CLIENT_VERSION}
headers["User-Agent"] = []string{constant.CLIENT_VERSION}
headers["Accept-Encoding"] = []string{"gzip,deflate,sdch"}
//headers["Accept-Encoding"] = []string{"gzip,deflate,sdch"}
headers["Connection"] = []string{"Keep-Alive"}
headers["RequestId"] = []string{uuid.NewV4().String()}
uid, err := uuid.NewV4()
if err != nil {
return
}
headers["RequestId"] = []string{uid.String()}
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, server.timeoutMs, params)
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
}
}
func (server *NacosServer) ReqApi(api string, params map[string]string, method string) (string, error) {
func (server *NacosServer) ReqConfigApi(api string, params map[string]string, headers map[string]string, method string, timeoutMS uint64) (string, error) {
srvs := server.serverList
if srvs == nil || len(srvs) == 0 {
return "", errors.New("server list is empty")
}
server.InjectSecurityInfo(params, security.BuildConfigResource(params["tenant"], params["group"], params["dataId"]))
//only one server,retry request when error
var err error
var result string
if len(srvs) == 1 {
for i := 0; i < constant.REQUEST_DOMAIN_RETRY_TIME; i++ {
result, err := server.callServer(api, params, method, getAddress(srvs[0]), srvs[0].ContextPath)
result, err = server.callConfigServer(api, params, headers, method, getAddress(srvs[0]), srvs[0].ContextPath, timeoutMS)
if err == nil {
return result, nil
}
log.Printf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%s> , result:<%s> \n", api, method, utils.ToJsonString(params), err.Error(), result)
logger.Errorf("api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s>", api, method, util.ToJsonString(params), err, result)
}
return "", errors.New("retry " + strconv.Itoa(constant.REQUEST_DOMAIN_RETRY_TIME) + " times request failed!")
} else {
index := rand.Intn(len(srvs))
for i := 1; i <= len(srvs); i++ {
curServer := srvs[index]
result, err := server.callServer(api, params, method, getAddress(curServer), curServer.ContextPath)
result, err = server.callConfigServer(api, params, headers, method, getAddress(curServer), curServer.ContextPath, timeoutMS)
if err == nil {
return result, nil
}
log.Printf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%s> , result:<%s> \n", api, method, utils.ToJsonString(params), err.Error(), result)
logger.Errorf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s> \n", api, method, util.ToJsonString(params), err, result)
index = (index + i) % len(srvs)
}
return "", errors.New("retry " + strconv.Itoa(constant.REQUEST_DOMAIN_RETRY_TIME) + " times request failed!")
}
return "", errors.Wrapf(err, "retry %d times request failed!", constant.REQUEST_DOMAIN_RETRY_TIME)
}
func (server *NacosServer) initRefreshSrvIfNeed() {
func (server *NacosServer) ReqApi(api string, params map[string]string, method string, config constant.ClientConfig) (string, error) {
srvs := server.serverList
if srvs == nil || len(srvs) == 0 {
return "", errors.New("server list is empty")
}
server.InjectSecurityInfo(params, security.BuildNamingResource(params["namespaceId"], params["serviceName"], params["groupName"]))
//only one server,retry request when error
var err error
var result string
if len(srvs) == 1 {
for i := 0; i < constant.REQUEST_DOMAIN_RETRY_TIME; i++ {
result, err = server.callServer(api, params, method, getAddress(srvs[0]), srvs[0].ContextPath)
if err == nil {
return result, nil
}
logger.Errorf("api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s>", api, method, util.ToJsonString(params), err, result)
}
} else {
index := rand.Intn(len(srvs))
for i := 1; i <= len(srvs); i++ {
curServer := srvs[index]
result, err = server.callServer(api, params, method, getAddress(curServer), curServer.ContextPath)
if err == nil {
return result, nil
}
logger.Errorf("api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s>", api, method, util.ToJsonString(params), err, result)
index = (index + i) % len(srvs)
}
}
return "", errors.Wrapf(err, "retry %d times request failed!", constant.REQUEST_DOMAIN_RETRY_TIME)
}
func (server *NacosServer) initRefreshSrvIfNeed(ctx context.Context) {
if server.endpoint == "" {
return
}
server.refreshServerSrvIfNeed()
if len(strings.TrimSpace(server.endpointContextPath)) == 0 {
server.endpointContextPath = "nacos"
}
if len(strings.TrimSpace(server.clusterName)) == 0 {
server.clusterName = "serverlist"
}
urlString := "http://" + server.endpoint + "/" + strings.TrimSpace(server.endpointContextPath) + "/" + strings.TrimSpace(server.clusterName)
if len(strings.TrimSpace(server.endpointQueryParams)) != 0 {
urlString += "?" + server.endpointQueryParams
}
logger.Infof("nacos address server url: <%s>", urlString)
server.refreshServerSrvIfNeed(urlString, server.endpointQueryHeader)
go func() {
time.Sleep(time.Duration(1) * time.Second)
server.refreshServerSrvIfNeed()
for {
select {
case <-ctx.Done():
return
default:
time.Sleep(time.Duration(10) * time.Second)
server.refreshServerSrvIfNeed(urlString, server.endpointQueryHeader)
}
}
}()
}
func (server *NacosServer) refreshServerSrvIfNeed() {
if utils.CurrentMillis()-server.lastSrvRefTime < server.vipSrvRefInterMills && len(server.serverList) > 0 {
func (server *NacosServer) refreshServerSrvIfNeed(urlString string, header map[string][]string) {
if util.CurrentMillis()-server.lastSrvRefTime < server.vipSrvRefInterMills && len(server.serverList) > 0 {
return
}
var list []string
urlString := "http://" + server.endpoint + "/nacos/serverlist"
result := server.httpAgent.RequestOnlyResult(http.MethodGet, urlString, nil, server.timeoutMs, nil)
result := server.httpAgent.RequestOnlyResult(http.MethodGet, urlString, header, server.timeoutMs, nil)
list = strings.Split(result, "\n")
log.Printf("[info] http nacos server list: <%s> \n", result)
var servers []constant.ServerConfig
contextPath := server.contextPath
if len(contextPath) == 0 {
contextPath = constant.WEB_CONTEXT
}
for _, line := range list {
if line != "" {
splitLine := strings.Split(strings.TrimSpace(line), ":")
port := 80
port := 8848
var err error
if len(splitLine) == 2 {
port, err = strconv.Atoi(splitLine[1])
if err != nil {
log.Printf("[ERROR] get port from server:<%s> error: <%s> \n", line, err.Error())
logger.Errorf("get port from server:<%s> error: <%+v>", line, err)
continue
}
}
servers = append(servers, constant.ServerConfig{IpAddr: splitLine[0], Port: uint64(port), ContextPath: constant.WEB_CONTEXT})
servers = append(servers, constant.ServerConfig{Scheme: constant.DEFAULT_SERVER_SCHEME, IpAddr: splitLine[0], Port: uint64(port), ContextPath: contextPath})
}
}
if len(servers) > 0 {
server.Lock()
if !reflect.DeepEqual(server.serverList, servers) {
log.Printf("[info] server list is updated, old: <%v>,new:<%v> \n", server.serverList, servers)
}
server.serverList = servers
server.lastSrvRefTime = utils.CurrentMillis()
server.Unlock()
}
server.Lock()
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()
}
}
return
}
func getAddress(cfg constant.ServerConfig) string {
return cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
func (server *NacosServer) GetServerList() []constant.ServerConfig {
return server.serverList
}
func (server *NacosServer) InjectSecurityInfo(param map[string]string, resource security.RequestResource) {
securityInfo := server.securityLogin.GetSecurityInfo(resource)
for k, v := range securityInfo {
param[k] = v
}
}
func getAddress(cfg constant.ServerConfig) string {
if strings.Index(cfg.IpAddr, "http://") >= 0 || strings.Index(cfg.IpAddr, "https://") >= 0 {
return cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
}
return cfg.Scheme + "://" + cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
}
func (server *NacosServer) GetNextServer() (constant.ServerConfig, error) {
serverLen := len(server.GetServerList())
if serverLen == 0 {
return constant.ServerConfig{}, errors.New("server is empty")
}
index := atomic.AddInt32(&server.currentIndex, 1) % int32(serverLen)
return server.GetServerList()[index], nil
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package nacos_server
import (
"context"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/stretchr/testify/assert"
)
func Test_getAddressWithScheme(t *testing.T) {
var serverConfigTest = constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "console.nacos.io",
Scheme: "https",
}
address := getAddress(serverConfigTest)
assert.Equal(t, "https://console.nacos.io:80", address)
}
func Test_getAddressWithoutScheme(t *testing.T) {
serverConfigTest := constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "http://console.nacos.io",
}
assert.Equal(t, "http://console.nacos.io:80", getAddress(serverConfigTest))
serverConfigTest.IpAddr = "https://console.nacos.io"
assert.Equal(t, "https://console.nacos.io:80", getAddress(serverConfigTest))
}
func buildNacosServer(clientConfig constant.ClientConfig) (*NacosServer, error) {
return NewNacosServer(context.Background(),
[]constant.ServerConfig{*constant.NewServerConfig("http://console.nacos.io", 80)},
clientConfig,
&http_agent.HttpAgent{},
1000,
"",
nil)
}
func TestNacosServer_InjectSignForNamingHttp_NoAk(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "",
SecretKey: "",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
param["groupName"] = "g-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Empty(t, param["ak"])
assert.Empty(t, param["data"])
assert.Empty(t, param["signature"])
}
func TestNacosServer_InjectSignForNamingHttp_WithGroup(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
param["groupName"] = "g-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.Contains(t, param["data"], "@@g-0@@s-0")
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_InjectSignForNamingHttp_WithoutGroup(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["serviceName"] = "s-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.NotContains(t, param["data"], "@@g-0@@s-0")
assert.Contains(t, param["data"], "@@s-0")
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_InjectSignForNamingHttp_WithoutServiceName(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
param["groupName"] = "g-0"
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["groupName"], param["serviceName"]))
assert.Equal(t, "123", param["ak"])
assert.Contains(t, param["data"], "@@")
assert.Regexp(t, "\\d+", param["data"])
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_InjectSignForNamingHttp_WithoutServiceNameAndGroup(t *testing.T) {
clientConfig := constant.ClientConfig{
AccessKey: "123",
SecretKey: "321",
}
server, err := buildNacosServer(clientConfig)
if err != nil {
t.FailNow()
}
param := make(map[string]string, 4)
server.InjectSecurityInfo(param, security.BuildNamingResource(param["namespaceId"], param["serviceName"], param["groupName"]))
assert.Equal(t, "123", param["ak"])
assert.NotContains(t, param["data"], "@@")
assert.Regexp(t, "\\d+", param["data"])
_, has := param["signature"]
assert.True(t, has)
}
func TestNacosServer_UpdateServerListForSecurityLogin(t *testing.T) {
endpoint := "console.nacos.io:80"
clientConfig := constant.ClientConfig{
Username: "nacos",
Password: "nacos",
NamespaceId: "namespace_1",
Endpoint: endpoint,
EndpointContextPath: "nacos",
ClusterName: "serverlist",
AppendToStdout: true,
}
server, err := NewNacosServer(context.Background(),
nil,
clientConfig,
&http_agent.HttpAgent{},
1000,
endpoint,
nil)
if err != nil {
t.FailNow()
}
nacosAuthClient := server.securityLogin.Clients[0]
client, ok := nacosAuthClient.(*security.NacosAuthClient)
assert.True(t, ok)
assert.Equal(t, server.GetServerList(), client.GetServerList())
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rpc
import (
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"google.golang.org/grpc"
)
type IConnection interface {
request(request rpc_request.IRequest, timeoutMills int64, client *RpcClient) (rpc_response.IResponse, error)
close()
getConnectionId() string
getServerInfo() ServerInfo
setAbandon(flag bool)
getAbandon() bool
}
type Connection struct {
conn *grpc.ClientConn
connectionId string
abandon bool
serverInfo ServerInfo
}
func (c *Connection) getConnectionId() string {
return c.connectionId
}
func (c *Connection) getServerInfo() ServerInfo {
return c.serverInfo
}
func (c *Connection) setAbandon(flag bool) {
c.abandon = flag
}
func (c *Connection) getAbandon() bool {
return c.abandon
}
func (c *Connection) close() {
_ = c.conn.Close()
}

View File

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

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