This commit is contained in:
binbin.zhang 2022-07-30 17:21:35 +08:00 committed by GitHub
parent dec3976954
commit 373906a4be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 6560 additions and 3006 deletions

View File

@ -14,8 +14,7 @@ jobs:
strategy:
matrix:
config:
- go_version: 1.13
- go_version: 1.14
- go_version: 1.15
steps:
- name: Set up Go 1.x
@ -24,6 +23,10 @@ jobs:
go-version: ${{ matrix.config.go_version }}
id: go
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Check out code into the Go module directory
uses: actions/checkout@v2

330
README.md
View File

@ -9,51 +9,55 @@
Nacos-sdk-go for Go client allows you to access Nacos service,it supports service discovery and dynamic configuration.
## Requirements
Supported Go version over 1.12
Supported Nacos version over 1.x
Supported Go version over 1.15
Supported Nacos version over 2.x
## Installation
Use `go get` to install SDK
```sh
$ go get -u github.com/nacos-group/nacos-sdk-go
$ go get -u github.com/nacos-group/nacos-sdk-go/v2
```
## Quick Examples
* ClientConfig
```go
constant.ClientConfig{
TimeoutMs uint64 // timeout for requesting Nacos server, default value is 10000ms
NamespaceId string // the namespaceId of Nacos.When namespace is public, fill in the blank string here.
AppName string // the appName
Endpoint string // the endpoint for get Nacos server addresses
RegionId string // the regionId for kms
AccessKey string // the AccessKey for kms
SecretKey string // the SecretKey for kms
OpenKMS bool // it's to open kms,default is false. https://help.aliyun.com/product/28933.html
CacheDir string // the directory for persist nacos service info,default value is current path
UpdateThreadNum int // the number of gorutine for update nacos service info,default value is 20
NotLoadCacheAtStart bool // not to load persistent nacos service info in CacheDir at start time
UpdateCacheWhenEmpty bool // update cache when get empty service instance from server
Username string // the username for nacos auth
Password string // the password for nacos auth
LogDir string // the directory for log, default is current path
LogLevel string // the level of log, it's must be debug,info,warn,error, default value is info
LogSampling *ClientLogSamplingConfig // the sampling config of log
ContextPath string // the nacos server contextpath
LogRollingConfig *ClientLogRollingConfig // the log rolling config
TimeoutMs uint64 // timeout for requesting Nacos server, default value is 10000ms
NamespaceId string // the namespaceId of Nacos
Endpoint string // the endpoint for ACM. https://help.aliyun.com/document_detail/130146.html
RegionId string // the regionId for ACM & KMS
AccessKey string // the AccessKey for ACM & KMS
SecretKey string // the SecretKey for ACM & KMS
OpenKMS bool // it's to open KMS, default is false. https://help.aliyun.com/product/28933.html
// , to enable encrypt/decrypt, DataId should be start with "cipher-"
CacheDir string // the directory for persist nacos service info,default value is current path
UpdateThreadNum int // the number of goroutine for update nacos service info,default value is 20
NotLoadCacheAtStart bool // not to load persistent nacos service info in CacheDir at start time
UpdateCacheWhenEmpty bool // update cache when get empty service instance from server
Username string // the username for nacos auth
Password string // the password for nacos auth
LogDir string // the directory for log, default is current path
RotateTime string // the rotate time for log, eg: 30m, 1h, 24h, default is 24h
MaxAge int64 // the max age of a log file, default value is 3
LogLevel string // the level of log, it's must be debug,info,warn,error, default value is info
}
```
* ServerConfig
```go
constant.ServerConfig{
ContextPath string // the nacos server context path
IpAddr string // the nacos server address
Port uint64 // the nacos server port
Scheme string // the nacos server scheme
Scheme string // the nacos server scheme,defaut=http,this is not required in 2.0
ContextPath string // the nacos server contextpath,defaut=/nacos,this is not required in 2.0
IpAddr string // the nacos server address
Port uint64 // nacos server port
GrpcPort uint64 // nacos server grpc port, default=server port + 1000, this is not required
}
```
@ -64,108 +68,108 @@ constant.ServerConfig{
```go
//create clientConfig
clientConfig := constant.ClientConfig{
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", //we can create multiple clients with different namespaceId to support multiple namespace.When namespace is public, fill in the blank string here.
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
LogLevel: "debug",
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"),
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",
},
{
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")
),
*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,
_, _ := clients.CreateNamingClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
})
// Create config client for dynamic configuration
_, _ = clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": serverConfigs,
"clientConfig": clientConfig,
_, _ := 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,
},
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,
},
vo.NacosClientParam{
ClientConfig: &clientConfig,
ServerConfigs: serverConfigs,
},
)
```
### Create client for ACM
https://help.aliyun.com/document_detail/130146.html
```go
cc := constant.ClientConfig{
Endpoint: "acm.aliyun.com:8080",
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468",
RegionId: "cn-shanghai",
AccessKey: "LTAI4G8KxxxxxxxxxxxxxbwZLBr",
SecretKey: "n5jTL9YxxxxxxxxxxxxaxmPLZV9",
OpenKMS: true,
TimeoutMs: 5000,
LogLevel: "debug",
Endpoint: "acm.aliyun.com:8080",
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468",
RegionId: "cn-shanghai",
AccessKey: "LTAI4G8KxxxxxxxxxxxxxbwZLBr",
SecretKey: "n5jTL9YxxxxxxxxxxxxaxmPLZV9",
OpenKMS: true,
TimeoutMs: 5000,
LogLevel: "debug",
}
// a more graceful way to create config client
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
},
vo.NacosClientParam{
ClientConfig: &cc,
},
)
```
### Service Discovery
* Register instanceRegisterInstance
@ -173,16 +177,16 @@ client, err := clients.NewConfigClient(
```go
success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc":"shanghai"},
ClusterName: "cluster-a", // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc":"shanghai"},
ClusterName: "cluster-a", // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
```
@ -192,12 +196,12 @@ success, err := namingClient.RegisterInstance(vo.RegisterInstanceParam{
```go
success, err := namingClient.DeregisterInstance(vo.DeregisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Ephemeral: true,
Cluster: "cluster-a", // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Ephemeral: true,
Cluster: "cluster-a", // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
```
@ -207,9 +211,9 @@ success, err := namingClient.DeregisterInstance(vo.DeregisterInstanceParam{
```go
services, err := namingClient.GetService(vo.GetServiceParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-a"}, // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
ServiceName: "demo.go",
Clusters: []string{"cluster-a"}, // default value is DEFAULT
GroupName: "group-a", // default value is DEFAULT_GROUP
})
```
@ -219,9 +223,9 @@ services, err := namingClient.GetService(vo.GetServiceParam{
```go
// SelectAllInstance return all instances,include healthy=false,enable=false,weight<=0
instances, err := namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
})
```
@ -231,10 +235,10 @@ instances, err := namingClient.SelectAllInstances(vo.SelectAllInstancesParam{
```go
// SelectInstances only return the instances of healthy=${HealthyOnly},enable=true and weight>0
instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
HealthyOnly: true,
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
HealthyOnly: true,
})
```
@ -245,9 +249,9 @@ instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
// 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
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
})
```
@ -259,12 +263,12 @@ instance, err := namingClient.SelectOneHealthyInstance(vo.SelectOneHealthInstanc
// Subscribe key = serviceName+groupName+cluster
// Note: We call add multiple SubscribeCallback with the same key.
err := namingClient.Subscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
SubscribeCallback: func (services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
```
@ -274,24 +278,25 @@ err := namingClient.Subscribe(vo.SubscribeParam{
```go
err := namingClient.Unsubscribe(vo.SubscribeParam{
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
SubscribeCallback: func(services []model.SubscribeService, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
ServiceName: "demo.go",
GroupName: "group-a", // default value is DEFAULT_GROUP
Clusters: []string{"cluster-a"}, // default value is DEFAULT
SubscribeCallback: func (services []model.Instance, err error) {
log.Printf("\n\n callback return services:%s \n\n", utils.ToJsonString(services))
},
})
```
* Get all services name:GetAllServicesInfo
```go
serviceInfos, err := client.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,
}),
serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,
}),
```
@ -302,9 +307,9 @@ serviceInfos, err := client.GetAllServicesInfo(vo.GetAllServiceInfoParam{
```go
success, err := configClient.PublishConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
DataId: "dataId",
Group: "group",
Content: "hello world!222222"})
```
@ -313,8 +318,8 @@ success, err := configClient.PublishConfig(vo.ConfigParam{
```go
success, err = configClient.DeleteConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: "dataId",
Group: "group"})
```
@ -323,8 +328,8 @@ success, err = configClient.DeleteConfig(vo.ConfigParam{
```go
content, err := configClient.GetConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group"})
DataId: "dataId",
Group: "group"})
```
@ -333,56 +338,65 @@ content, err := configClient.GetConfig(vo.ConfigParam{
```go
err := configClient.ListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
},
DataId: "dataId",
Group: "group",
OnChange: func (namespace, group, dataId, data string) {
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
},
})
```
* Cancel the listening of config change eventCancelListenConfig
```go
err := configClient.CancelListenConfig(vo.ConfigParam{
DataId: "dataId",
Group: "group",
DataId: "dataId",
Group: "group",
})
```
* Search config: SearchConfig
```go
configPage, err := configClient.SearchConfig(vo.SearchConfigParam{
Search: "blur",
DataId: "",
Group: "",
PageNo: 1,
PageSize: 10,
Search: "blur",
DataId: "",
Group: "",
PageNo: 1,
PageSize: 10,
})
```
## Example
We can run example to learn how to use nacos go client.
* [Config Example](./example/config)
* [Naming Example](./example/service)
## Documentation
You can view the open-api documentation from the [Nacos open-api wepsite](https://nacos.io/en-us/docs/open-api.html).
You can view the full documentation from the [Nacos website](https://nacos.io/en-us/docs/what-is-nacos.html).
## Contributing
Contributors are welcomed to join Nacos-sdk-go project. Please check [CONTRIBUTING.md](./CONTRIBUTING.md) about how to contribute to this project.
Contributors are welcomed to join Nacos-sdk-go project. Please check [CONTRIBUTING.md](./CONTRIBUTING.md) about how to
contribute to this project.
## Contact
* Join us from DingDing Group(23191211).
* [Gitter](https://gitter.im/alibaba/nacos): Nacos's IM tool for community messaging, collaboration and discovery.
* [Twitter](https://twitter.com/nacos2): Follow along for latest nacos news on Twitter.
* [Weibo](https://weibo.com/u/6574374908): Follow along for latest nacos news on Weibo (Twitter of China version).
* [Nacos SegmentFault](https://segmentfault.com/t/nacos): Get the latest notice and prompt help from SegmentFault.
* Email Group:
* users-nacos@googlegroups.com: Nacos usage general discussion.
* dev-nacos@googlegroups.com: Nacos developer discussion (APIs, feature design, etc).
* commits-nacos@googlegroups.com: Commits notice, very high frequency.
* users-nacos@googlegroups.com: Nacos usage general discussion.
* dev-nacos@googlegroups.com: Nacos developer discussion (APIs, feature design, etc).
* commits-nacos@googlegroups.com: Commits notice, very high frequency.

View File

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

View File

@ -0,0 +1,467 @@
/*
* 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.
// source: nacos_grpc_service.proto
package grpc
import (
context "context"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
any "github.com/golang/protobuf/ptypes/any"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Metadata struct {
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,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Metadata) Reset() { *m = Metadata{} }
func (m *Metadata) String() string { return proto.CompactTextString(m) }
func (*Metadata) ProtoMessage() {}
func (*Metadata) Descriptor() ([]byte, []int) {
return fileDescriptor_f908b146bdb05ce9, []int{0}
}
func (m *Metadata) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Metadata.Unmarshal(m, b)
}
func (m *Metadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Metadata.Marshal(b, m, deterministic)
}
func (m *Metadata) XXX_Merge(src proto.Message) {
xxx_messageInfo_Metadata.Merge(m, src)
}
func (m *Metadata) XXX_Size() int {
return xxx_messageInfo_Metadata.Size(m)
}
func (m *Metadata) XXX_DiscardUnknown() {
xxx_messageInfo_Metadata.DiscardUnknown(m)
}
var xxx_messageInfo_Metadata proto.InternalMessageInfo
func (m *Metadata) GetType() string {
if m != nil {
return m.Type
}
return ""
}
func (m *Metadata) GetClientIp() string {
if m != nil {
return m.ClientIp
}
return ""
}
func (m *Metadata) GetHeaders() map[string]string {
if m != nil {
return m.Headers
}
return nil
}
type Payload struct {
Metadata *Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"`
Body *any.Any `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Payload) Reset() { *m = Payload{} }
func (m *Payload) String() string { return proto.CompactTextString(m) }
func (*Payload) ProtoMessage() {}
func (*Payload) Descriptor() ([]byte, []int) {
return fileDescriptor_f908b146bdb05ce9, []int{1}
}
func (m *Payload) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Payload.Unmarshal(m, b)
}
func (m *Payload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Payload.Marshal(b, m, deterministic)
}
func (m *Payload) XXX_Merge(src proto.Message) {
xxx_messageInfo_Payload.Merge(m, src)
}
func (m *Payload) XXX_Size() int {
return xxx_messageInfo_Payload.Size(m)
}
func (m *Payload) XXX_DiscardUnknown() {
xxx_messageInfo_Payload.DiscardUnknown(m)
}
var xxx_messageInfo_Payload proto.InternalMessageInfo
func (m *Payload) GetMetadata() *Metadata {
if m != nil {
return m.Metadata
}
return nil
}
func (m *Payload) GetBody() *any.Any {
if m != nil {
return m.Body
}
return nil
}
func init() {
proto.RegisterType((*Metadata)(nil), "Metadata")
proto.RegisterMapType((map[string]string)(nil), "Metadata.HeadersEntry")
proto.RegisterType((*Payload)(nil), "Payload")
}
func init() { proto.RegisterFile("nacos_grpc_service.proto", fileDescriptor_f908b146bdb05ce9) }
var fileDescriptor_f908b146bdb05ce9 = []byte{
// 333 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x51, 0x4f, 0x4b, 0xeb, 0x40,
0x10, 0x7f, 0xdb, 0xf6, 0xbd, 0xa4, 0xd3, 0x57, 0x2a, 0x4b, 0x91, 0x98, 0x4b, 0x4b, 0x45, 0x0c,
0x0a, 0xdb, 0x12, 0x2f, 0xa5, 0x07, 0xc1, 0x82, 0xa0, 0x07, 0xa1, 0xc4, 0x9b, 0x97, 0x32, 0x49,
0xd6, 0x1a, 0x4c, 0xb3, 0x71, 0xb3, 0x29, 0xec, 0x37, 0xf2, 0x63, 0x4a, 0x37, 0x69, 0xb0, 0x20,
0xde, 0x66, 0x7e, 0x7f, 0xe6, 0xc7, 0xcc, 0x80, 0x93, 0x61, 0x24, 0x8a, 0xf5, 0x46, 0xe6, 0xd1,
0xba, 0xe0, 0x72, 0x97, 0x44, 0x9c, 0xe5, 0x52, 0x28, 0xe1, 0x9e, 0x6d, 0x84, 0xd8, 0xa4, 0x7c,
0x6a, 0xba, 0xb0, 0x7c, 0x9d, 0x62, 0xa6, 0x2b, 0x6a, 0xf2, 0x49, 0xc0, 0x7e, 0xe2, 0x0a, 0x63,
0x54, 0x48, 0x29, 0x74, 0x94, 0xce, 0xb9, 0xd3, 0x1e, 0x13, 0xaf, 0x1b, 0x98, 0x9a, 0xba, 0x60,
0x47, 0x69, 0xc2, 0x33, 0xf5, 0x98, 0x3b, 0xb6, 0xc1, 0x9b, 0x9e, 0xce, 0xc0, 0x7a, 0xe3, 0x18,
0x73, 0x59, 0x38, 0xd6, 0xb8, 0xed, 0xf5, 0xfc, 0x53, 0x76, 0x98, 0xc5, 0x1e, 0x2a, 0xe2, 0x3e,
0x53, 0x52, 0x07, 0x07, 0x99, 0xbb, 0x80, 0xff, 0xdf, 0x09, 0x7a, 0x02, 0xed, 0x77, 0xae, 0x1d,
0x62, 0x06, 0xef, 0x4b, 0x3a, 0x84, 0xbf, 0x3b, 0x4c, 0x4b, 0xee, 0xb4, 0x0c, 0x56, 0x35, 0x8b,
0xd6, 0x9c, 0x4c, 0x5e, 0xc0, 0x5a, 0xa1, 0x4e, 0x05, 0xc6, 0xf4, 0x02, 0xec, 0x6d, 0x1d, 0x64,
0x74, 0x3d, 0xbf, 0xdb, 0x24, 0x07, 0x0d, 0x45, 0x3d, 0xe8, 0x84, 0x22, 0xd6, 0x66, 0x9f, 0x9e,
0x3f, 0x64, 0xd5, 0x19, 0xd8, 0xe1, 0x0c, 0xec, 0x2e, 0xd3, 0x81, 0x51, 0xf8, 0x73, 0xe8, 0x07,
0xfc, 0xa3, 0xe4, 0x85, 0x7a, 0x56, 0x92, 0xe3, 0x96, 0x5e, 0x42, 0x5f, 0x1e, 0x01, 0x36, 0xab,
0xc3, 0xdd, 0xa6, 0x9a, 0xfc, 0x99, 0x11, 0xff, 0x0a, 0xac, 0xda, 0x49, 0x47, 0x60, 0xd5, 0x9e,
0x9f, 0xd5, 0xfe, 0x2d, 0x0c, 0x96, 0xc9, 0x71, 0xce, 0x35, 0x0c, 0x6a, 0xcf, 0x32, 0xf9, 0x2d,
0xc9, 0x23, 0x33, 0xb2, 0x3c, 0x87, 0x51, 0x24, 0xb6, 0x0c, 0xd3, 0x24, 0xc4, 0x10, 0x99, 0xf9,
0x37, 0xc3, 0x3c, 0x61, 0xfb, 0x9f, 0x33, 0x2c, 0x95, 0x58, 0x91, 0xf0, 0x9f, 0x59, 0xef, 0xe6,
0x2b, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x9e, 0xc7, 0x2d, 0x0f, 0x02, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// RequestStreamClient is the client API for RequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RequestStreamClient interface {
// build a streamRequest
RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (RequestStream_RequestStreamClient, error)
}
type requestStreamClient struct {
cc *grpc.ClientConn
}
func NewRequestStreamClient(cc *grpc.ClientConn) RequestStreamClient {
return &requestStreamClient{cc}
}
func (c *requestStreamClient) RequestStream(ctx context.Context, in *Payload, opts ...grpc.CallOption) (RequestStream_RequestStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_RequestStream_serviceDesc.Streams[0], "/RequestStream/requestStream", opts...)
if err != nil {
return nil, err
}
x := &requestStreamRequestStreamClient{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
}
type RequestStream_RequestStreamClient interface {
Recv() (*Payload, error)
grpc.ClientStream
}
type requestStreamRequestStreamClient struct {
grpc.ClientStream
}
func (x *requestStreamRequestStreamClient) Recv() (*Payload, error) {
m := new(Payload)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// RequestStreamServer is the server API for RequestStream service.
type RequestStreamServer interface {
// build a streamRequest
RequestStream(*Payload, RequestStream_RequestStreamServer) error
}
// UnimplementedRequestStreamServer can be embedded to have forward compatible implementations.
type UnimplementedRequestStreamServer struct {
}
func (*UnimplementedRequestStreamServer) RequestStream(req *Payload, srv RequestStream_RequestStreamServer) error {
return status.Errorf(codes.Unimplemented, "method RequestStream not implemented")
}
func RegisterRequestStreamServer(s *grpc.Server, srv RequestStreamServer) {
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, &requestStreamRequestStreamServer{stream})
}
type RequestStream_RequestStreamServer interface {
Send(*Payload) error
grpc.ServerStream
}
type requestStreamRequestStreamServer struct {
grpc.ServerStream
}
func (x *requestStreamRequestStreamServer) Send(m *Payload) error {
return x.ServerStream.SendMsg(m)
}
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: "nacos_grpc_service.proto",
}
// RequestClient is the client API for Request service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type RequestClient interface {
// Sends a commonRequest
Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error)
}
type requestClient struct {
cc *grpc.ClientConn
}
func NewRequestClient(cc *grpc.ClientConn) RequestClient {
return &requestClient{cc}
}
func (c *requestClient) Request(ctx context.Context, in *Payload, opts ...grpc.CallOption) (*Payload, error) {
out := new(Payload)
err := c.cc.Invoke(ctx, "/Request/request", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// RequestServer is the server API for Request service.
type RequestServer interface {
// Sends a commonRequest
Request(context.Context, *Payload) (*Payload, error)
}
// UnimplementedRequestServer can be embedded to have forward compatible implementations.
type UnimplementedRequestServer struct {
}
func (*UnimplementedRequestServer) Request(ctx context.Context, req *Payload) (*Payload, error) {
return nil, status.Errorf(codes.Unimplemented, "method Request not implemented")
}
func RegisterRequestServer(s *grpc.Server, srv RequestServer) {
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",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RequestServer).Request(ctx, req.(*Payload))
}
return interceptor(ctx, in, info, handler)
}
var _Request_serviceDesc = grpc.ServiceDesc{
ServiceName: "Request",
HandlerType: (*RequestServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "request",
Handler: _Request_Request_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "nacos_grpc_service.proto",
}
// BiRequestStreamClient is the client API for BiRequestStream service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BiRequestStreamClient interface {
// Sends a commonRequest
RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (BiRequestStream_RequestBiStreamClient, error)
}
type biRequestStreamClient struct {
cc *grpc.ClientConn
}
func NewBiRequestStreamClient(cc *grpc.ClientConn) BiRequestStreamClient {
return &biRequestStreamClient{cc}
}
func (c *biRequestStreamClient) RequestBiStream(ctx context.Context, opts ...grpc.CallOption) (BiRequestStream_RequestBiStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_BiRequestStream_serviceDesc.Streams[0], "/BiRequestStream/requestBiStream", opts...)
if err != nil {
return nil, err
}
x := &biRequestStreamRequestBiStreamClient{stream}
return x, nil
}
type BiRequestStream_RequestBiStreamClient interface {
Send(*Payload) error
Recv() (*Payload, error)
grpc.ClientStream
}
type biRequestStreamRequestBiStreamClient struct {
grpc.ClientStream
}
func (x *biRequestStreamRequestBiStreamClient) Send(m *Payload) error {
return x.ClientStream.SendMsg(m)
}
func (x *biRequestStreamRequestBiStreamClient) Recv() (*Payload, error) {
m := new(Payload)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// BiRequestStreamServer is the server API for BiRequestStream service.
type BiRequestStreamServer interface {
// Sends a commonRequest
RequestBiStream(BiRequestStream_RequestBiStreamServer) error
}
// UnimplementedBiRequestStreamServer can be embedded to have forward compatible implementations.
type UnimplementedBiRequestStreamServer struct {
}
func (*UnimplementedBiRequestStreamServer) RequestBiStream(srv BiRequestStream_RequestBiStreamServer) error {
return status.Errorf(codes.Unimplemented, "method RequestBiStream not implemented")
}
func RegisterBiRequestStreamServer(s *grpc.Server, srv BiRequestStreamServer) {
s.RegisterService(&_BiRequestStream_serviceDesc, srv)
}
func _BiRequestStream_RequestBiStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(BiRequestStreamServer).RequestBiStream(&biRequestStreamRequestBiStreamServer{stream})
}
type BiRequestStream_RequestBiStreamServer interface {
Send(*Payload) error
Recv() (*Payload, error)
grpc.ServerStream
}
type biRequestStreamRequestBiStreamServer struct {
grpc.ServerStream
}
func (x *biRequestStreamRequestBiStreamServer) Send(m *Payload) error {
return x.ServerStream.SendMsg(m)
}
func (x *biRequestStreamRequestBiStreamServer) Recv() (*Payload, error) {
m := new(Payload)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
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: "nacos_grpc_service.proto",
}

View File

@ -0,0 +1,53 @@
/*
* 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";
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

@ -21,37 +21,32 @@ import (
"fmt"
"io/ioutil"
"os"
"runtime"
"strconv"
"strings"
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/file"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
)
func GetFileName(cacheKey string, cacheDir string) string {
if runtime.GOOS == constant.OS_WINDOWS {
cacheKey = strings.ReplaceAll(cacheKey, ":", constant.WINDOWS_LEGAL_NAME_SPLITER)
}
return cacheDir + string(os.PathSeparator) + cacheKey
}
func WriteServicesToFile(service model.Service, cacheDir string) {
file.MkdirIfNecessary(cacheDir)
sb, _ := json.Marshal(service)
domFileName := GetFileName(util.GetServiceCacheKey(service.Name, service.Clusters), cacheDir)
err := ioutil.WriteFile(domFileName, sb, 0666)
func WriteServicesToFile(service *model.Service, cacheKey, cacheDir string) {
err := file.MkdirIfNecessary(cacheDir)
if err != nil {
logger.Errorf("failed to write name cache:%s ,value:%s ,err:%+v", domFileName, string(sb), err)
logger.Errorf("mkdir cacheDir failed,cacheDir:%s,err:", cacheDir, err)
return
}
bytes, _ := json.Marshal(service)
domFileName := GetFileName(cacheKey, cacheDir)
err = ioutil.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 {
@ -75,21 +70,29 @@ func ReadServicesFromFile(cacheDir string) map[string]model.Service {
if service == nil {
continue
}
serviceMap[f.Name()] = *service
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
serviceMap[cacheKey] = *service
}
logger.Info("finish loading name cache, total: " + strconv.Itoa(len(files)))
logger.Infof("finish loading name cache, total: %s", strconv.Itoa(len(files)))
return serviceMap
}
func WriteConfigToFile(cacheKey string, cacheDir string, content string) {
file.MkdirIfNecessary(cacheDir)
fileName := GetFileName(cacheKey, cacheDir)
if len(content) == 0 {
// delete config snapshot
if err := os.Remove(fileName); err != nil {
logger.Errorf("failed to delete config file,cache:%s ,value:%s ,err:%+v", fileName, content, err)
}
return
}
err := ioutil.WriteFile(fileName, []byte(content), 0666)
if err != nil {
logger.Errorf("failed to write config cache:%s ,value:%s ,err:%+v", fileName, content, err)
}
}
func ReadConfigFromFile(cacheKey string, cacheDir string) (string, error) {
@ -100,3 +103,18 @@ func ReadConfigFromFile(cacheKey string, cacheDir string) (string, error) {
}
return string(b), nil
}
// GetFailover , get failover content
func GetFailover(key, dir string) string {
filePath := dir + string(os.PathSeparator) + key + constant.FAILOVER_FILE_SUFFIX
if !file.IsExistFile(filePath) {
return ""
}
logger.GetLogger().Warn(fmt.Sprintf("reading failover content from path:%s", filePath))
fileContent, err := ioutil.ReadFile(filePath)
if err != nil {
logger.GetLogger().Error(fmt.Sprintf("fail to read failover content from %s", filePath))
return ""
}
return string(fileContent)
}

View File

@ -1,36 +1,38 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cache
import (
"runtime"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/stretchr/testify/assert"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
)
func TestGetFileName(t *testing.T) {
name := GetFileName("nacos@@providers:org.apache.dubbo.UserProvider:hangzhou", "tmp")
if runtime.GOOS == constant.OS_WINDOWS {
assert.Equal(t, name, "tmp\\nacos@@providers&&org.apache.dubbo.UserProvider&&hangzhou")
} else {
assert.Equal(t, name, "tmp/nacos@@providers:org.apache.dubbo.UserProvider:hangzhou")
}
func TestGetFailover(t *testing.T) {
cacheKey := "test_failOver"
dir := file.GetCurrentPath()
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 ioutil.WriteFile(filepath, []byte(content), 0666)
}

View File

@ -17,14 +17,15 @@
package clients
import (
"errors"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// CreateConfigClient use to create config client
@ -61,7 +62,7 @@ func NewNamingClient(param vo.NacosClientParam) (iClient naming_client.INamingCl
if err != nil {
return
}
iClient = &naming
iClient = naming
return
}
@ -84,9 +85,15 @@ func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, er
client := &nacos_client.NacosClient{}
if param.ClientConfig == nil {
// default clientConfig
_ = client.SetClientConfig(constant.ClientConfig{})
_ = client.SetClientConfig(constant.ClientConfig{
TimeoutMs: 10 * 1000,
BeatInterval: 5 * 1000,
})
} else {
_ = client.SetClientConfig(*param.ClientConfig)
err = client.SetClientConfig(*param.ClientConfig)
if err != nil {
return nil, err
}
}
if len(param.ServerConfigs) == 0 {
@ -95,7 +102,7 @@ func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, er
err = errors.New("server configs not found in properties")
return nil, err
}
_ = client.SetServerConfig([]constant.ServerConfig{})
_ = client.SetServerConfig(nil)
} else {
err = client.SetServerConfig(param.ServerConfigs)
if err != nil {
@ -104,7 +111,9 @@ func setConfig(param vo.NacosClientParam) (iClient nacos_client.INacosClient, er
}
if _, _err := client.GetHttpAgent(); _err != nil {
_ = client.SetHttpAgent(&http_agent.HttpAgent{})
if clientCfg, err := client.GetClientConfig(); err == nil {
_ = client.SetHttpAgent(&http_agent.HttpAgent{TlsConfig: clientCfg.TLSCfg})
}
}
iClient = client
return

View File

@ -1,26 +1,42 @@
package clients
import (
"net"
"reflect"
"testing"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
func TestSetConfigClient(t *testing.T) {
func getIntranetIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "127.0.0.1"
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return "127.0.0.1"
}
func TestSetConfigClient(t *testing.T) {
ip := getIntranetIP()
sc := []constant.ServerConfig{
*constant.NewServerConfig(
"console.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos")),
ip,
8848,
),
}
cc := *constant.NewClientConfig(
constant.WithNamespaceId("e525eafa-f7d7-4029-83d9-008937f9d468"),
constant.WithNamespaceId("public"),
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
@ -49,5 +65,4 @@ func TestSetConfigClient(t *testing.T) {
assert.Nil(t, err)
assert.True(t, reflect.DeepEqual(nacosClientFromMap, nacosClientFromStruct))
})
}

View File

@ -17,57 +17,62 @@
package config_client
import (
"errors"
"fmt"
"math"
"net/url"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"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/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"
)
type ConfigClient struct {
nacos_client.INacosClient
kmsClient *kms.Client
localConfigs []vo.ConfigParam
mutex sync.Mutex
configProxy ConfigProxy
configCacheDir string
currentTaskCount int32
cacheMap cache.ConcurrentMap
schedulerMap cache.ConcurrentMap
}
const (
perTaskConfigSize = 3000
executorErrDelay = 5 * time.Second
)
type ConfigClient struct {
nacos_client.INacosClient
kmsClient *kms.Client
localConfigs []vo.ConfigParam
mutex sync.Mutex
configProxy IConfigProxy
configCacheDir string
lastAllSyncTime time.Time
cacheMap cache.ConcurrentMap
uid string
listenExecute chan struct{}
}
type cacheData struct {
isInitializing bool
dataId string
group string
content string
contentType string
tenant string
cacheDataListener *cacheDataListener
md5 string
appName string
taskId int
configClient *ConfigClient
isSyncWithServer bool
}
type cacheDataListener struct {
@ -76,70 +81,56 @@ type cacheDataListener struct {
}
func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
config := &ConfigClient{
cacheMap: cache.NewConcurrentMap(),
schedulerMap: cache.NewConcurrentMap(),
}
config.schedulerMap.Set("root", true)
go config.delayScheduler(time.NewTimer(1*time.Millisecond), 500*time.Millisecond, "root", config.listenConfigExecutor())
config := &ConfigClient{}
config.INacosClient = nc
clientConfig, err := nc.GetClientConfig()
if err != nil {
return config, err
return nil, err
}
serverConfig, err := nc.GetServerConfig()
if err != nil {
return config, err
return nil, err
}
httpAgent, err := nc.GetHttpAgent()
if err != nil {
return config, err
return nil, err
}
loggerConfig := logger.Config{
LogFileName: constant.LOG_FILE_NAME,
Level: clientConfig.LogLevel,
Sampling: clientConfig.LogSampling,
LogRollingConfig: clientConfig.LogRollingConfig,
LogDir: clientConfig.LogDir,
CustomLogger: clientConfig.CustomLogger,
LogStdout: clientConfig.LogStdout,
if err = initLogger(clientConfig); err != nil {
return nil, err
}
err = logger.InitLogger(loggerConfig)
if err != nil {
return config, err
clientConfig.CacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config"
config.configCacheDir = clientConfig.CacheDir
if config.configProxy, err = NewConfigProxy(serverConfig, clientConfig, httpAgent); err != nil {
return nil, err
}
logger.GetLogger().Infof("logDir:<%s> cacheDir:<%s>", clientConfig.LogDir, clientConfig.CacheDir)
config.configCacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config"
config.configProxy, err = NewConfigProxy(serverConfig, clientConfig, httpAgent)
if clientConfig.OpenKMS {
kmsClient, err := kms.NewClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
if err != nil {
return config, err
return nil, err
}
config.kmsClient = kmsClient
}
uid, err := uuid.NewV4()
if err != nil {
return nil, err
}
config.uid = uid.String()
config.cacheMap = cache.NewConcurrentMap()
// maximum buffered queue to prevent chan deadlocks during frequent configuration file updates
config.listenExecute = make(chan struct{}, math.MaxInt64)
config.startInternal()
return config, err
}
func (client *ConfigClient) sync() (clientConfig constant.ClientConfig,
serverConfigs []constant.ServerConfig, agent http_agent.IHttpAgent, err error) {
clientConfig, err = client.GetClientConfig()
if err != nil {
logger.Errorf("getClientConfig catch error:%+v", err)
return
}
serverConfigs, err = client.GetServerConfig()
if err != nil {
logger.Errorf("getServerConfig catch error:%+v", err)
return
}
agent, err = client.GetHttpAgent()
if err != nil {
logger.Errorf("getHttpAgent catch error:%+v", err)
}
return
func initLogger(clientConfig constant.ClientConfig) error {
return logger.InitLogger(logger.BuildLoggerConfig(clientConfig))
}
func (client *ConfigClient) GetConfig(param vo.ConfigParam) (content string, err error) {
@ -174,7 +165,7 @@ func (client *ConfigClient) encrypt(dataId, content string) (string, error) {
request.Method = "POST"
request.Scheme = "https"
request.AcceptFormat = "json"
request.KeyId = "alias/acs/acm" // use default key
request.KeyId = "alias/acs/mse" // use default key
request.Plaintext = content
response, err := client.kmsClient.Encrypt(request)
if err != nil {
@ -191,56 +182,61 @@ func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content string
return "", err
}
if len(param.Group) <= 0 {
err = errors.New("[client.GetConfig] param.group can not be empty")
return "", err
param.Group = constant.DEFAULT_GROUP
}
//todo 获取容灾配置的 EncryptedDataKey LocalEncryptedDataKeyProcessor.getEncryptDataKeyFailover
clientConfig, _ := client.GetClientConfig()
cacheKey := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
content, err = client.configProxy.GetConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
content = cache.GetFailover(cacheKey, client.configCacheDir)
if len(content) > 0 {
logger.GetLogger().Warn(fmt.Sprintf("%s %s %s is using failover content!", clientConfig.NamespaceId, param.Group, param.DataId))
return content, nil
}
response, err := client.configProxy.queryConfig(param.DataId, param.Group, clientConfig.NamespaceId,
clientConfig.TimeoutMs, false, client)
if err != nil {
logger.Errorf("get config from server error:%+v ", err)
if _, ok := err.(*nacos_error.NacosError); ok {
nacosErr := err.(*nacos_error.NacosError)
if nacosErr.ErrorCode() == "404" {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, "")
logger.Warnf("[client.GetConfig] config not found, dataId: %s, group: %s, namespaceId: %s.", param.DataId, param.Group, clientConfig.NamespaceId)
return "", nil
}
if nacosErr.ErrorCode() == "403" {
return "", errors.New("get config forbidden")
}
}
logger.Infof("get config from server error:%+v ", err)
content, err = cache.ReadConfigFromFile(cacheKey, client.configCacheDir)
if err != nil {
logger.Errorf("get config from cache error:%+v ", err)
return "", errors.New("read config from both server and cache fail")
}
} else {
cache.WriteConfigToFile(cacheKey, client.configCacheDir, content)
return content, nil
}
return content, nil
return response.Content, nil
}
func (client *ConfigClient) PublishConfig(param vo.ConfigParam) (published bool,
err error) {
func (client *ConfigClient) PublishConfig(param vo.ConfigParam) (published bool, err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.PublishConfig] param.dataId can not be empty")
}
if len(param.Group) <= 0 {
err = errors.New("[client.PublishConfig] param.group can not be empty")
return
}
if len(param.Content) <= 0 {
err = errors.New("[client.PublishConfig] param.content can not be empty")
return
}
param.Content, err = client.encrypt(param.DataId, param.Content)
if err != nil {
return false, err
if len(param.Group) <= 0 {
param.Group = constant.DEFAULT_GROUP
}
if param.Content, err = client.encrypt(param.DataId, param.Content); err != nil {
return
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.PublishConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
request := rpc_request.NewConfigPublishRequest(param.Group, param.DataId, clientConfig.NamespaceId, param.Content, param.CasMd5)
request.AdditionMap["tag"] = param.Tag
request.AdditionMap["appName"] = param.AppName
request.AdditionMap["betaIps"] = param.BetaIps
request.AdditionMap["type"] = param.Type
request.AdditionMap["encryptedDataKey"] = param.EncryptedDataKey
rpcClient := client.configProxy.getRpcClient(client)
response, err := client.configProxy.requestProxy(rpcClient, request, constant.DEFAULT_TIMEOUT_MILLS)
if response != nil {
return response.IsSuccess(), err
}
return false, err
}
func (client *ConfigClient) DeleteConfig(param vo.ConfigParam) (deleted bool, err error) {
@ -248,14 +244,22 @@ func (client *ConfigClient) DeleteConfig(param vo.ConfigParam) (deleted bool, er
err = errors.New("[client.DeleteConfig] param.dataId can not be empty")
}
if len(param.Group) <= 0 {
err = errors.New("[client.DeleteConfig] param.group can not be empty")
param.Group = constant.DEFAULT_GROUP
}
if err != nil {
return false, err
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.DeleteConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
request := rpc_request.NewConfigRemoveRequest(param.Group, param.DataId, clientConfig.NamespaceId)
rpcClient := client.configProxy.getRpcClient(client)
response, err := client.configProxy.requestProxy(rpcClient, request, constant.DEFAULT_TIMEOUT_MILLS)
if response != nil {
return response.IsSuccess(), err
}
return false, err
}
// Cancel Listen Config
//Cancel Listen Config
func (client *ConfigClient) CancelListenConfig(param vo.ConfigParam) (err error) {
clientConfig, err := client.GetClientConfig()
if err != nil {
@ -264,32 +268,9 @@ func (client *ConfigClient) CancelListenConfig(param vo.ConfigParam) (err error)
}
client.cacheMap.Remove(util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId))
logger.Infof("Cancel listen config DataId:%s Group:%s", param.DataId, param.Group)
remakeId := int(math.Ceil(float64(client.cacheMap.Count()) / float64(perTaskConfigSize)))
currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))
if remakeId < currentTaskCount {
client.remakeCacheDataTaskId(remakeId)
}
return err
}
// Remake cache data taskId
func (client *ConfigClient) remakeCacheDataTaskId(remakeId int) {
for i := 0; i < remakeId; i++ {
count := 0
for _, key := range client.cacheMap.Keys() {
if count == perTaskConfigSize {
break
}
if value, ok := client.cacheMap.Get(key); ok {
cData := value.(cacheData)
cData.taskId = i
client.cacheMap.Set(key, cData)
}
count++
}
}
}
func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.ListenConfig] DataId can not be empty")
@ -306,16 +287,17 @@ func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
}
key := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
var cData cacheData
var cData *cacheData
if v, ok := client.cacheMap.Get(key); ok {
cData = v.(cacheData)
cData = v.(*cacheData)
cData.isInitializing = true
} else {
var (
content string
md5Str string
)
if content, _ = cache.ReadConfigFromFile(key, client.configCacheDir); len(content) > 0 {
content, _ = cache.ReadConfigFromFile(key, client.configCacheDir)
if len(content) > 0 {
md5Str = util.Md5(content)
}
listener := &cacheDataListener{
@ -323,7 +305,7 @@ func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
lastMd5: md5Str,
}
cData = cacheData{
cData = &cacheData{
isInitializing: true,
dataId: param.DataId,
group: param.Group,
@ -332,191 +314,22 @@ func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
md5: md5Str,
cacheDataListener: listener,
taskId: client.cacheMap.Count() / perTaskConfigSize,
configClient: client,
}
}
client.cacheMap.Set(key, cData)
return
}
// Delay Scheduler
// initialDelay the time to delay first execution
// delay the delay between the termination of one execution and the commencement of the next
func (client *ConfigClient) delayScheduler(t *time.Timer, delay time.Duration, taskId string, execute func() error) {
for {
if v, ok := client.schedulerMap.Get(taskId); ok {
if !v.(bool) {
return
}
}
<-t.C
d := delay
if err := execute(); err != nil {
d = executorErrDelay
}
t.Reset(d)
}
}
// Listen for the configuration executor
func (client *ConfigClient) listenConfigExecutor() func() error {
return func() error {
listenerSize := client.cacheMap.Count()
taskCount := int(math.Ceil(float64(listenerSize) / float64(perTaskConfigSize)))
currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))
if taskCount > currentTaskCount {
for i := currentTaskCount; i < taskCount; i++ {
client.schedulerMap.Set(strconv.Itoa(i), true)
go client.delayScheduler(time.NewTimer(1*time.Millisecond), 10*time.Millisecond, strconv.Itoa(i), client.longPulling(i))
}
atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
} else if taskCount < currentTaskCount {
for i := taskCount; i < currentTaskCount; i++ {
if _, ok := client.schedulerMap.Get(strconv.Itoa(i)); ok {
client.schedulerMap.Set(strconv.Itoa(i), false)
}
}
atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
}
return nil
}
}
// Long polling listening configuration
func (client *ConfigClient) longPulling(taskId int) func() error {
return func() error {
var listeningConfigs string
initializationList := make([]cacheData, 0)
for _, key := range client.cacheMap.Keys() {
if value, ok := client.cacheMap.Get(key); ok {
cData := value.(cacheData)
if cData.taskId == taskId {
if cData.isInitializing {
initializationList = append(initializationList, cData)
}
if len(cData.tenant) > 0 {
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
cData.md5 + constant.SPLIT_CONFIG_INNER + cData.tenant + constant.SPLIT_CONFIG
} else {
listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
cData.md5 + constant.SPLIT_CONFIG
}
}
}
}
if len(listeningConfigs) > 0 {
clientConfig, err := client.GetClientConfig()
if err != nil {
logger.Errorf("[checkConfigInfo.GetClientConfig] err: %+v", err)
return err
}
// http get
params := make(map[string]string)
params[constant.KEY_LISTEN_CONFIGS] = listeningConfigs
var changed string
changedTmp, err := client.configProxy.ListenConfig(params, len(initializationList) > 0, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err == nil {
changed = changedTmp
} else {
if _, ok := err.(*nacos_error.NacosError); ok {
changed = changedTmp
} else {
logger.Errorf("[client.ListenConfig] listen config error: %+v", err)
}
return err
}
for _, v := range initializationList {
v.isInitializing = false
client.cacheMap.Set(util.GetConfigCacheKey(v.dataId, v.group, v.tenant), v)
}
if len(strings.ToLower(strings.Trim(changed, " "))) == 0 {
logger.Info("[client.ListenConfig] no change")
} else {
logger.Info("[client.ListenConfig] config changed:" + changed)
client.callListener(changed, clientConfig.NamespaceId)
}
}
return nil
}
}
// Execute the Listener callback func()
func (client *ConfigClient) callListener(changed, tenant string) {
changedDecoded, _ := url.QueryUnescape(changed)
changedConfigs := strings.Split(changedDecoded, "\u0001")
for _, config := range changedConfigs {
attrs := strings.Split(config, "\u0002")
if len(attrs) >= 2 {
if value, ok := client.cacheMap.Get(util.GetConfigCacheKey(attrs[0], attrs[1], tenant)); ok {
cData := value.(cacheData)
content, err := client.getConfigInner(vo.ConfigParam{
DataId: cData.dataId,
Group: cData.group,
})
if err != nil {
logger.Errorf("[client.getConfigInner] DataId:[%s] Group:[%s] Error:[%+v]", cData.dataId, cData.group, err)
continue
}
cData.content = content
cData.md5 = util.Md5(content)
if cData.md5 != cData.cacheDataListener.lastMd5 {
go cData.cacheDataListener.listener(tenant, attrs[1], attrs[0], cData.content)
cData.cacheDataListener.lastMd5 = cData.md5
client.cacheMap.Set(util.GetConfigCacheKey(cData.dataId, cData.group, tenant), cData)
}
}
}
}
}
func (client *ConfigClient) buildBasePath(serverConfig constant.ServerConfig) (basePath string) {
basePath = "http://" + serverConfig.IpAddr + ":" +
strconv.FormatUint(serverConfig.Port, 10) + serverConfig.ContextPath + constant.CONFIG_PATH
return
}
func (client *ConfigClient) SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error) {
func (client *ConfigClient) SearchConfig(param vo.SearchConfigParm) (*model.ConfigPage, error) {
return client.searchConfigInner(param)
}
func (client *ConfigClient) PublishAggr(param vo.ConfigParam) (published bool,
err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.PublishAggr] param.dataId can not be empty")
}
if len(param.Group) <= 0 {
err = errors.New("[client.PublishAggr] param.group can not be empty")
}
if len(param.Content) <= 0 {
err = errors.New("[client.PublishAggr] param.content can not be empty")
}
if len(param.DatumId) <= 0 {
err = errors.New("[client.PublishAggr] param.DatumId can not be empty")
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.PublishAggProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
func (client *ConfigClient) CloseClient() {
client.configProxy.getRpcClient(client).Shutdown()
}
func (client *ConfigClient) RemoveAggr(param vo.ConfigParam) (published bool,
err error) {
if len(param.DataId) <= 0 {
err = errors.New("[client.DeleteAggr] param.dataId can not be empty")
}
if len(param.Group) <= 0 {
err = errors.New("[client.DeleteAggr] param.group can not be empty")
}
if len(param.Content) <= 0 {
err = errors.New("[client.DeleteAggr] param.content can not be empty")
}
if len(param.DatumId) <= 0 {
err = errors.New("[client.DeleteAggr] param.DatumId can not be empty")
}
clientConfig, _ := client.GetClientConfig()
return client.configProxy.DeleteAggProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
}
func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*model.ConfigPage, error) {
func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParm) (*model.ConfigPage, error) {
if param.Search != "accurate" && param.Search != "blur" {
return nil, errors.New("[client.searchConfigInner] param.search must be accurate or blur")
}
@ -527,13 +340,13 @@ func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*mode
param.PageSize = 10
}
clientConfig, _ := client.GetClientConfig()
configItems, err := client.configProxy.SearchConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
configItems, err := client.configProxy.searchConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
if err != nil {
logger.Errorf("search config from server error:%+v ", err)
if _, ok := err.(*nacos_error.NacosError); ok {
nacosErr := err.(*nacos_error.NacosError)
if nacosErr.ErrorCode() == "404" {
return nil, nil
return nil, errors.New("config not found")
}
if nacosErr.ErrorCode() == "403" {
return nil, errors.New("get config forbidden")
@ -543,3 +356,130 @@ func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*mode
}
return configItems, nil
}
func (client *ConfigClient) startInternal() {
timer := time.NewTimer(executorErrDelay)
go func() {
for {
select {
case <-client.listenExecute:
client.executeConfigListen()
case <-timer.C:
client.executeConfigListen()
}
timer.Reset(executorErrDelay)
}
}()
}
func (client *ConfigClient) executeConfigListen() {
listenCachesMap := make(map[int][]*cacheData, 16)
needAllSync := time.Since(client.lastAllSyncTime) >= constant.ALL_SYNC_INTERNAL
for _, v := range client.cacheMap.Items() {
cache, ok := v.(*cacheData)
if !ok {
continue
}
if cache.isSyncWithServer {
if cache.md5 != cache.cacheDataListener.lastMd5 {
go cache.cacheDataListener.listener(cache.tenant, cache.group, cache.dataId, cache.content)
cache.cacheDataListener.lastMd5 = cache.md5
}
if !needAllSync {
continue
}
}
var cacheDatas []*cacheData
if cacheDatas, ok = listenCachesMap[cache.taskId]; ok {
cacheDatas = append(cacheDatas, cache)
} else {
cacheDatas = append(cacheDatas, cache)
}
listenCachesMap[cache.taskId] = cacheDatas
}
hasChangedKeys := false
if len(listenCachesMap) > 0 {
for taskId, listenCaches := range listenCachesMap {
request := buildConfigBatchListenRequest(listenCaches)
rpcClient := client.configProxy.createRpcClient(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 && !iResponse.IsSuccess() {
continue
}
changeKeys := make(map[string]struct{})
if response, ok := iResponse.(*rpc_response.ConfigChangeBatchListenResponse); ok {
if len(response.ChangedConfigs) > 0 {
hasChangedKeys = true
for _, v := range response.ChangedConfigs {
changeKey := util.GetConfigCacheKey(v.DataId, v.Group, v.Tenant)
changeKeys[changeKey] = struct{}{}
if cache, ok := client.cacheMap.Get(changeKey); !ok {
continue
} else {
cacheData := cache.(*cacheData)
client.refreshContentAndCheck(cacheData, !cacheData.isInitializing)
}
}
}
for _, v := range listenCaches {
changeKey := util.GetConfigCacheKey(v.dataId, v.group, v.tenant)
if _, ok := changeKeys[changeKey]; !ok {
v.isSyncWithServer = true
continue
}
v.isInitializing = true
}
}
}
}
if needAllSync {
client.lastAllSyncTime = time.Now()
}
if hasChangedKeys {
client.notifyListenConfig()
}
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
}
cacheData.content = configQueryResponse.Content
cacheData.contentType = configQueryResponse.ContentType
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 {
go cacheData.cacheDataListener.listener(cacheData.tenant, cacheData.group, cacheData.dataId, cacheData.content)
cacheData.cacheDataListener.lastMd5 = cacheData.md5
client.cacheMap.Set(util.GetConfigCacheKey(cacheData.dataId, cacheData.group, cacheData.tenant), cacheData)
}
}
func (client *ConfigClient) notifyListenConfig() {
client.listenExecute <- struct{}{}
}

View File

@ -17,8 +17,8 @@
package config_client
import (
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
//go:generate mockgen -destination ../../mock/mock_config_client_interface.go -package mock -source=./config_client_interface.go
@ -63,7 +63,8 @@ type IConfigClient interface {
// tenant ==>nacos.namespace optional
// pageNo option,default is 1
// pageSize option,default is 10
SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error)
SearchConfig(param vo.SearchConfigParm) (*model.ConfigPage, error)
PublishAggr(param vo.ConfigParam) (published bool, err error)
// CloseClient Close the GRPC client
CloseClient()
}

View File

@ -18,132 +18,97 @@ package config_client
import (
"errors"
"net/http"
"runtime"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/mock"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/sirupsen/logrus"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
var goVersion = runtime.Version()
var clientConfigTest = constant.ClientConfig{
TimeoutMs: 10000,
ListenInterval: 20000,
BeatInterval: 10000,
}
var clientConfigTestWithTenant = constant.ClientConfig{
TimeoutMs: 10000,
ListenInterval: 20000,
BeatInterval: 10000,
NamespaceId: "tenant",
}
var serverConfigTest = constant.ServerConfig{
ContextPath: "/nacos",
Port: 80,
IpAddr: "console.nacos.io",
}
var serverConfigWithOptions = constant.NewServerConfig("console.nacos.io", 80, constant.WithContextPath("/nacos"))
var serverConfigWithOptions = constant.NewServerConfig("127.0.0.1", 80, constant.WithContextPath("/nacos"))
var clientConfigWithOptions = constant.NewClientConfig(
constant.WithCustomLogger(mockLogger{}),
constant.WithTimeoutMs(10*1000),
constant.WithBeatInterval(2*1000),
constant.WithNotLoadCacheAtStart(true),
)
var (
dataIdKey = goVersion + "dataId"
groupKey = goVersion + "group:env"
configNoChangeKey = goVersion + "ConfigNoChange"
multipleClientsKey = goVersion + "MultipleClients"
multipleClientsMultipleConfigsKey = goVersion + "MultipleClientsMultipleConfig"
cancelOneKey = goVersion + "CancelOne"
cancelOne1Key = goVersion + "CancelOne1"
cancelListenConfigKey = goVersion + "cancel_listen_config"
specialSymbolKey = goVersion + "special_symbol"
)
var configParamMapTest = map[string]string{
"dataId": dataIdKey,
"group": groupKey,
}
var configParamTest = vo.ConfigParam{
DataId: dataIdKey,
Group: groupKey,
}
var localConfigTest = vo.ConfigParam{
DataId: dataIdKey,
Group: groupKey,
DataId: "dataId",
Group: "group",
Content: "content",
}
var localConfigMapTest = map[string]string{
"dataId": dataIdKey,
"group": groupKey,
"content": "content",
}
var client *ConfigClient
func TestMain(m *testing.M) {
func createConfigClientTest() *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
nc.SetClientConfig(*clientConfigWithOptions)
nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ = NewConfigClient(&nc)
m.Run()
}
func createConfigClientHttpTest(mockHttpAgent http_agent.IHttpAgent) *ConfigClient {
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
nc.SetClientConfig(clientConfigTest)
nc.SetHttpAgent(mockHttpAgent)
_ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
_ = nc.SetClientConfig(*clientConfigWithOptions)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewConfigClient(&nc)
client.configProxy = &MockConfigProxy{}
return client
}
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"}, nil
}
func (m *MockConfigProxy) searchConfigProxy(param vo.SearchConfigParm, 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(taskId string, client *ConfigClient) *rpc.RpcClient {
return &rpc.RpcClient{}
}
func (m *MockConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient {
return &rpc.RpcClient{}
}
func Test_GetConfig(t *testing.T) {
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "group",
Content: "hello world!222222"})
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: dataIdKey,
DataId: localConfigTest.DataId,
Group: "group"})
assert.Nil(t, err)
assert.Equal(t, "hello world!222222", content)
assert.Equal(t, "hello world", content)
}
func Test_SearchConfig(t *testing.T) {
client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "groDEFAULT_GROUPup",
Content: "hello world!222222"})
configPage, err := client.SearchConfig(vo.SearchConfigParam{
client := createConfigClientTest()
_, _ = client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
Content: "hello world"})
configPage, err := client.SearchConfig(vo.SearchConfigParm{
Search: "accurate",
DataId: dataIdKey,
Group: "groDEFAULT_GROUPup",
DataId: localConfigTest.DataId,
Group: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 10,
})
@ -153,6 +118,7 @@ func Test_SearchConfig(t *testing.T) {
// PublishConfig
func Test_PublishConfigWithoutDataId(t *testing.T) {
client := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -161,18 +127,10 @@ func Test_PublishConfigWithoutDataId(t *testing.T) {
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutGroup(t *testing.T) {
_, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "",
Content: "content",
})
assert.NotNil(t, err)
}
func Test_PublishConfigWithoutContent(t *testing.T) {
client := createConfigClientTest()
_, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
DataId: localConfigTest.DataId,
Group: "group",
Content: "",
})
@ -180,66 +138,25 @@ func Test_PublishConfigWithoutContent(t *testing.T) {
}
func Test_PublishConfig(t *testing.T) {
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
DataId: localConfigTest.DataId,
Group: "group",
Content: "hello world2!"})
Content: "hello world"})
assert.Nil(t, err)
assert.True(t, success)
}
func Test_PublishConfigWithType(t *testing.T) {
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "group",
Content: "foo",
Type: vo.YAML,
})
assert.Nil(t, err)
assert.True(t, success)
}
func Test_PublishConfigWithErrorResponse(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
clientHttp := createConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodPost),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(localConfigMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no security"), nil)
success, err := clientHttp.PublishConfig(localConfigTest)
assert.NotNil(t, err)
assert.True(t, !success)
}
func Test_PublishConfigWithErrorResponse_200(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
clientHttp := createConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodPost),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(localConfigMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "false"), nil)
success, err := clientHttp.PublishConfig(localConfigTest)
assert.NotNil(t, err)
assert.True(t, !success)
}
// DeleteConfig
func Test_DeleteConfig(t *testing.T) {
client := createConfigClientTest()
success, err := client.PublishConfig(vo.ConfigParam{
DataId: dataIdKey,
DataId: localConfigTest.DataId,
Group: "group",
Content: "hello world!"})
@ -247,50 +164,15 @@ func Test_DeleteConfig(t *testing.T) {
assert.True(t, success)
success, err = client.DeleteConfig(vo.ConfigParam{
DataId: dataIdKey,
DataId: localConfigTest.DataId,
Group: "group"})
assert.Nil(t, err)
assert.True(t, success)
}
func Test_DeleteConfigWithErrorResponse_200(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
clientHttp := createConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodDelete),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(1).Return(http_agent.FakeHttpResponse(200, "false"), nil)
success, err := clientHttp.DeleteConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithErrorResponse_401(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
mockHttpAgent := mock.NewMockIHttpAgent(controller)
clientHttp := createConfigClientHttpTest(mockHttpAgent)
mockHttpAgent.EXPECT().Request(gomock.Eq(http.MethodDelete),
gomock.Eq("http://console.nacos.io:80/nacos/v1/cs/configs"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(clientConfigTest.TimeoutMs),
gomock.Eq(configParamMapTest),
).Times(3).Return(http_agent.FakeHttpResponse(401, "no security"), nil)
success, err := clientHttp.DeleteConfig(configParamTest)
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutDataId(t *testing.T) {
client := createConfigClientTest()
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: "",
Group: "group",
@ -299,206 +181,54 @@ func Test_DeleteConfigWithoutDataId(t *testing.T) {
assert.Equal(t, false, success)
}
func Test_DeleteConfigWithoutGroup(t *testing.T) {
success, err := client.DeleteConfig(vo.ConfigParam{
DataId: dataIdKey,
Group: "",
})
assert.NotNil(t, err)
assert.Equal(t, false, success)
}
// ListenConfig
func TestListen(t *testing.T) {
// ListenConfig
t.Run("TestListenConfig", func(t *testing.T) {
key := util.GetConfigCacheKey(localConfigTest.DataId, localConfigTest.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, "")
var err error
var success bool
ch := make(chan string)
err = client.ListenConfig(vo.ConfigParam{
client := createConfigClientTest()
err := client.ListenConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
ch <- data
},
})
assert.Nil(t, err)
success, err = client.PublishConfig(vo.ConfigParam{
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
assert.Nil(t, err)
assert.Equal(t, true, success)
select {
case c := <-ch:
assert.Equal(t, c, localConfigTest.Content)
case <-time.After(10 * time.Second):
assert.Errorf(t, errors.New("timeout"), "timeout")
}
})
// ListenConfig no dataId
t.Run("TestListenConfigNoDataId", func(t *testing.T) {
listenConfigParam := vo.ConfigParam{
Group: "gateway",
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
},
}
client := createConfigClientTest()
err := client.ListenConfig(listenConfigParam)
assert.Error(t, err)
})
// ListenConfig no change
t.Run("TestListenConfigNoChange", func(t *testing.T) {
key := util.GetConfigCacheKey(configNoChangeKey, localConfigTest.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, localConfigTest.Content)
}
// CancelListenConfig
func TestCancelListenConfig(t *testing.T) {
//Multiple listeners listen for different configurations, cancel one
t.Run("TestMultipleListenersCancelOne", func(t *testing.T) {
client := createConfigClientTest()
var err error
var success bool
var content string
err = client.ListenConfig(vo.ConfigParam{
DataId: configNoChangeKey,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
content = "data"
},
})
assert.Nil(t, err)
success, err = client.PublishConfig(vo.ConfigParam{
DataId: configNoChangeKey,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
assert.Nil(t, err)
assert.Equal(t, true, success)
assert.Equal(t, content, "")
})
// Multiple clients listen to the same configuration file
t.Run("TestListenConfigWithMultipleClients", func(t *testing.T) {
ch := make(chan string)
listenConfigParam := vo.ConfigParam{
DataId: multipleClientsKey,
DataId: localConfigTest.DataId,
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
ch <- data
},
}
key := util.GetConfigCacheKey(listenConfigParam.DataId, listenConfigParam.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, "")
client.ListenConfig(listenConfigParam)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
nc.SetClientConfig(*clientConfigWithOptions)
nc.SetHttpAgent(&http_agent.HttpAgent{})
client1, _ := NewConfigClient(&nc)
client1.ListenConfig(listenConfigParam)
success, err := client.PublishConfig(vo.ConfigParam{
DataId: multipleClientsKey,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
assert.Nil(t, err)
assert.Equal(t, true, success)
select {
case c := <-ch:
assert.Equal(t, localConfigTest.Content, c)
case <-time.After(10 * time.Second):
assert.Errorf(t, errors.New("timeout"), "timeout")
}
})
// Multiple clients listen to multiple configuration files
t.Run("TestListenConfigWithMultipleClientsMultipleConfig", func(t *testing.T) {
ch := make(chan string)
listenConfigParam := vo.ConfigParam{
DataId: multipleClientsMultipleConfigsKey,
listenConfigParam1 := vo.ConfigParam{
DataId: localConfigTest.DataId + "1",
Group: localConfigTest.Group,
OnChange: func(namespace, group, dataId, data string) {
ch <- data
},
}
key := util.GetConfigCacheKey(listenConfigParam.DataId, listenConfigParam.Group, clientConfigTest.NamespaceId)
cache.WriteConfigToFile(key, client.configCacheDir, "")
client.ListenConfig(listenConfigParam)
_ = client.ListenConfig(listenConfigParam)
nc := nacos_client.NacosClient{}
nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions})
nc.SetClientConfig(*clientConfigWithOptions)
nc.SetHttpAgent(&http_agent.HttpAgent{})
client1, _ := NewConfigClient(&nc)
client1.ListenConfig(listenConfigParam)
success, err := client.PublishConfig(vo.ConfigParam{
DataId: multipleClientsMultipleConfigsKey,
Group: localConfigTest.Group,
Content: localConfigTest.Content})
_ = client.ListenConfig(listenConfigParam1)
err = client.CancelListenConfig(listenConfigParam)
assert.Nil(t, err)
assert.Equal(t, true, success)
select {
case c := <-ch:
assert.Equal(t, localConfigTest.Content, c)
case <-time.After(10 * time.Second):
assert.Errorf(t, errors.New("timeout"), "timeout")
}
})
}
func TestGetConfigWithSpecialSymbol(t *testing.T) {
contentStr := "hello world!!@#$%^&&*()"
success, err := client.PublishConfig(vo.ConfigParam{
DataId: specialSymbolKey,
Group: localConfigTest.Group,
Content: contentStr})
assert.Nil(t, err)
assert.True(t, success)
content, err := client.GetConfig(vo.ConfigParam{
DataId: specialSymbolKey,
Group: localConfigTest.Group})
assert.Nil(t, err)
assert.Equal(t, contentStr, content)
}
type mockLogger struct {
}
func (m mockLogger) Info(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Warn(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Error(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Debug(args ...interface{}) {
logrus.Info(args...)
}
func (m mockLogger) Infof(format string, args ...interface{}) {
logrus.Infof(format, args...)
}
func (m mockLogger) Warnf(format string, args ...interface{}) {
logrus.Warnf(format, args...)
}
func (m mockLogger) Errorf(format string, args ...interface{}) {
logrus.Errorf(format, args...)
}
func (m mockLogger) Debugf(format string, args ...interface{}) {
logrus.Debugf("implement me")
}

View File

@ -18,18 +18,26 @@ package config_client
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"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/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/nacos_server"
"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"
)
type ConfigProxy struct {
@ -37,34 +45,37 @@ type ConfigProxy struct {
clientConfig constant.ClientConfig
}
func NewConfigProxy(serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent) (ConfigProxy, error) {
func NewConfigProxy(serverConfig []constant.ServerConfig, clientConfig constant.ClientConfig, httpAgent http_agent.IHttpAgent) (IConfigProxy, error) {
proxy := ConfigProxy{}
var err error
proxy.nacosServer, err = nacos_server.NewNacosServer(serverConfig, clientConfig, httpAgent, clientConfig.TimeoutMs, clientConfig.Endpoint)
proxy.clientConfig = clientConfig
return proxy, err
return &proxy, err
}
func (cp *ConfigProxy) GetServerList() []constant.ServerConfig {
return cp.nacosServer.GetServerList()
func (cp *ConfigProxy) requestProxy(rpcClient *rpc.RpcClient, request rpc_request.IRequest, timeoutMills uint64) (rpc_response.IResponse, error) {
start := time.Now()
cp.nacosServer.InjectSecurityInfo(request.GetHeaders())
cp.injectCommHeader(request.GetHeaders())
cp.nacosServer.InjectSkAk(request.GetHeaders(), cp.clientConfig)
signHeaders := nacos_server.GetSignHeadersFromRequest(request.(rpc_request.IConfigRequest), cp.clientConfig.SecretKey)
request.PutAllHeaders(signHeaders)
//todo Config Limiter
response, err := rpcClient.Request(request, int64(timeoutMills))
monitor.GetConfigRequestMonitor(constant.GRPC, request.GetRequestType(), rpc_response.GetGrpcResponseStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
return response, err
}
func (cp *ConfigProxy) GetConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (string, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodGet, cp.clientConfig.TimeoutMs)
return result, err
func (cp *ConfigProxy) injectCommHeader(param map[string]string) {
now := strconv.FormatInt(util.CurrentMillis(), 10)
param[constant.CLIENT_APPNAME_HEADER] = cp.clientConfig.AppName
param[constant.CLIENT_REQUEST_TS_HEADER] = now
param[constant.CLIENT_REQUEST_TOKEN_HEADER] = util.Md5(now + cp.clientConfig.AppKey)
param[constant.EX_CONFIG_INFO] = "true"
param[constant.CHARSET_KEY] = "utf-8"
}
func (cp *ConfigProxy) SearchConfigProxy(param vo.SearchConfigParam, tenant, accessKey, secretKey string) (*model.ConfigPage, error) {
func (cp *ConfigProxy) searchConfigProxy(param vo.SearchConfigParm, tenant, accessKey, secretKey string) (*model.ConfigPage, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
@ -89,98 +100,108 @@ func (cp *ConfigProxy) SearchConfigProxy(param vo.SearchConfigParam, tenant, acc
}
return &configPage, nil
}
func (cp *ConfigProxy) PublishConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodPost, cp.clientConfig.TimeoutMs)
func (cp *ConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) {
if group == "" {
group = constant.DEFAULT_GROUP
}
configQueryRequest := rpc_request.NewConfigQueryRequest(group, dataId, tenant)
configQueryRequest.Headers["notify"] = strconv.FormatBool(notify)
cacheKey := util.GetConfigCacheKey(dataId, group, tenant)
// use the same key of config file as the limit checker's key
if IsLimited(cacheKey) {
// return error when check limited
return nil, errors.New("ConfigQueryRequest is limited")
}
iResponse, err := cp.requestProxy(cp.getRpcClient(client), configQueryRequest, timeout)
if err != nil {
return false, errors.New("[client.PublishConfig] publish config failed:" + err.Error())
return nil, err
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
} else {
return false, errors.New("[client.PublishConfig] publish config failed:" + result)
response, ok := iResponse.(*rpc_response.ConfigQueryResponse)
if !ok {
return nil, errors.New("ConfigQueryRequest returns type error")
}
if response.IsSuccess() {
//todo LocalConfigInfoProcessor.saveSnapshot
cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, response.Content)
//todo LocalConfigInfoProcessor.saveEncryptDataKeySnapshot
if response.ContentType == "" {
response.ContentType = "text"
}
return response, nil
}
if response.GetErrorCode() == 300 {
//todo LocalConfigInfoProcessor.saveSnapshot
cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, "")
//todo LocalConfigInfoProcessor.saveEncryptDataKeySnapshot
return response, nil
}
if response.GetErrorCode() == 400 {
logger.Errorf(
"[config_rpc_client] [sub-server-error] get server config being modified concurrently, dataId=%s, group=%s, "+
"tenant=%s", dataId, group, tenant)
return nil, errors.New("data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant)
}
if response.GetErrorCode() > 0 {
logger.Errorf("[config_rpc_client] [sub-server-error] dataId=%s, group=%s, tenant=%s, code=%+v", dataId, group,
tenant, response)
}
return response, nil
}
func (cp *ConfigProxy) PublishAggProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
func (cp *ConfigProxy) createRpcClient(taskId string, client *ConfigClient) *rpc.RpcClient {
labels := map[string]string{
constant.LABEL_SOURCE: constant.LABEL_SOURCE_SDK,
constant.LABEL_MODULE: constant.LABEL_MODULE_CONFIG,
"taskId": taskId,
}
params["method"] = "addDatum"
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
_, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_AGG_PATH, params, headers, http.MethodPost, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.PublishAggProxy] publish agg failed:" + err.Error())
iRpcClient, _ := rpc.CreateClient("config-"+taskId+"-"+client.uid, rpc.GRPC, labels, cp.nacosServer)
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})
rpcClient.Tenant = cp.clientConfig.NamespaceId
rpcClient.Start()
}
return true, nil
return rpcClient
}
func (cp *ConfigProxy) DeleteAggProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
}
params["method"] = "deleteDatum"
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
_, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_AGG_PATH, params, headers, http.MethodPost, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.DeleteAggProxy] delete agg failed:" + err.Error())
}
return true, nil
func (cp *ConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient {
return cp.createRpcClient("0", client)
}
func (cp *ConfigProxy) DeleteConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) {
params := util.TransformObject2Param(param)
if len(tenant) > 0 {
params["tenant"] = tenant
}
var headers = map[string]string{}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_PATH, params, headers, http.MethodDelete, cp.clientConfig.TimeoutMs)
if err != nil {
return false, errors.New("[client.DeleteConfig] deleted config failed:" + err.Error())
}
if strings.ToLower(strings.Trim(result, " ")) == "true" {
return true, nil
} else {
return false, errors.New("[client.DeleteConfig] deleted config failed: " + string(result))
}
type ConfigChangeNotifyRequestHandler struct {
client *ConfigClient
}
func (cp *ConfigProxy) ListenConfig(params map[string]string, isInitializing bool, tenant, accessKey, secretKey string) (string, error) {
//fixed at 30000msavoid frequent request on the server
var listenInterval uint64 = 30000
headers := map[string]string{
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
"Long-Pulling-Timeout": strconv.FormatUint(listenInterval, 10),
}
if isInitializing {
headers["Long-Pulling-Timeout-No-Hangup"] = "true"
}
headers["accessKey"] = accessKey
headers["secretKey"] = secretKey
if len(tenant) > 0 {
params["tenant"] = tenant
}
logger.Infof("[client.ListenConfig] request params:%+v header:%+v \n", params, headers)
// In order to prevent the server from handling the delay of the client's long task,
// increase the client's read timeout to avoid this problem.
timeout := listenInterval + listenInterval/10
result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_LISTEN_PATH, params, headers, http.MethodPost, timeout)
return result, err
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 {
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.notifyListenConfig()
return &rpc_response.NotifySubscriberResponse{
Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS},
}
}
return nil
}

View File

@ -0,0 +1,17 @@
package config_client
import (
"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.SearchConfigParm, tenant, accessKey, secretKey string) (*model.ConfigPage, error)
requestProxy(rpcClient *rpc.RpcClient, request rpc_request.IRequest, timeoutMills uint64) (rpc_response.IResponse, error)
createRpcClient(taskId string, client *ConfigClient) *rpc.RpcClient
getRpcClient(client *ConfigClient) *rpc.RpcClient
}

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

View File

@ -17,14 +17,15 @@
package nacos_client
import (
"errors"
"log"
"os"
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/file"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
)
type NacosClient struct {
@ -60,21 +61,7 @@ func (client *NacosClient) SetClientConfig(config constant.ClientConfig) (err er
if config.LogDir == "" {
config.LogDir = file.GetCurrentPath() + string(os.PathSeparator) + "log"
}
if config.LogSampling != nil {
if config.LogSampling.Initial < 0 {
config.LogSampling.Initial = 100
}
if config.LogSampling.Thereafter < 0 {
config.LogSampling.Thereafter = 100
}
if config.LogSampling.Tick < 0 {
config.LogSampling.Tick = 10 * time.Second
}
}
log.Printf("[INFO] logDir:<%s> cacheDir:<%s>", config.LogDir, config.CacheDir)
client.clientConfig = config
client.clientConfigValid = true

View File

@ -17,8 +17,8 @@
package nacos_client
import (
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
)
//go:generate mockgen -destination mock_nacos_client_interface.go -package nacos_client -source=./nacos_client_interface.go

View File

@ -1,228 +0,0 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
)
type HostReactor struct {
serviceInfoMap cache.ConcurrentMap
cacheDir string
updateThreadNum int
serviceProxy NamingProxy
pushReceiver PushReceiver
subCallback SubscribeCallback
updateTimeMap cache.ConcurrentMap
updateCacheWhenEmpty bool
}
const Default_Update_Thread_Num = 20
func NewHostReactor(serviceProxy NamingProxy, cacheDir string, updateThreadNum int, notLoadCacheAtStart bool, subCallback SubscribeCallback, updateCacheWhenEmpty bool) HostReactor {
if updateThreadNum <= 0 {
updateThreadNum = Default_Update_Thread_Num
}
hr := HostReactor{
serviceProxy: serviceProxy,
cacheDir: cacheDir,
updateThreadNum: updateThreadNum,
serviceInfoMap: cache.NewConcurrentMap(),
subCallback: subCallback,
updateTimeMap: cache.NewConcurrentMap(),
updateCacheWhenEmpty: updateCacheWhenEmpty,
}
pr := NewPushReceiver(&hr)
hr.pushReceiver = *pr
if !notLoadCacheAtStart {
hr.loadCacheFromDisk()
}
go hr.asyncUpdateService()
return hr
}
func (hr *HostReactor) loadCacheFromDisk() {
serviceMap := cache.ReadServicesFromFile(hr.cacheDir)
if len(serviceMap) == 0 {
return
}
for k, v := range serviceMap {
hr.serviceInfoMap.Set(k, v)
}
}
func (hr *HostReactor) ProcessServiceJson(result string) {
service := util.JsonToService(result)
if service == nil {
return
}
cacheKey := util.GetServiceCacheKey(service.Name, service.Clusters)
oldDomain, ok := hr.serviceInfoMap.Get(cacheKey)
if ok && !hr.updateCacheWhenEmpty {
//if instance list is empty,not to update cache
if service.Hosts == nil || len(service.Hosts) == 0 {
logger.Errorf("do not have useful host, ignore it, name:%s", service.Name)
return
}
}
hr.updateTimeMap.Set(cacheKey, uint64(util.CurrentMillis()))
hr.serviceInfoMap.Set(cacheKey, *service)
oldService, serviceOk := oldDomain.(model.Service)
if !ok || ok && serviceOk && isServiceInstanceChanged(&oldService, service) {
if !ok {
logger.Info("service not found in cache " + cacheKey)
} else {
logger.Info("service key:%s was updated to:%s", cacheKey, util.ToJsonString(service))
}
cache.WriteServicesToFile(*service, hr.cacheDir)
hr.subCallback.ServiceChanged(service)
}
}
func (hr *HostReactor) GetServiceInfo(serviceName string, clusters string) (model.Service, error) {
key := util.GetServiceCacheKey(serviceName, clusters)
cacheService, ok := hr.serviceInfoMap.Get(key)
if !ok {
hr.updateServiceNow(serviceName, clusters)
if cacheService, ok = hr.serviceInfoMap.Get(key); !ok {
return model.Service{}, errors.New("get service info failed")
}
}
return cacheService.(model.Service), nil
}
func (hr *HostReactor) GetAllServiceInfo(nameSpace, groupName string, pageNo, pageSize uint32) model.ServiceList {
data := model.ServiceList{}
result, err := hr.serviceProxy.GetAllServiceInfoList(nameSpace, groupName, pageNo, pageSize)
if err != nil {
logger.Errorf("GetAllServiceInfoList return error!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d err:%+v",
nameSpace, groupName, pageNo, pageSize, err)
return data
}
if result == "" {
logger.Errorf("GetAllServiceInfoList result is empty!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d",
nameSpace, groupName, pageNo, pageSize)
return data
}
err = json.Unmarshal([]byte(result), &data)
if err != nil {
logger.Errorf("GetAllServiceInfoList result json.Unmarshal error!nameSpace:%s groupName:%s pageNo:%d, pageSize:%d",
nameSpace, groupName, pageNo, pageSize)
return data
}
return data
}
func (hr *HostReactor) updateServiceNow(serviceName, clusters string) {
result, err := hr.serviceProxy.QueryList(serviceName, clusters, hr.pushReceiver.port, false)
if err != nil {
logger.Errorf("QueryList return error!serviceName:%s cluster:%s err:%+v", serviceName, clusters, err)
return
}
if result == "" {
logger.Errorf("QueryList result is empty!serviceName:%s cluster:%s", serviceName, clusters)
return
}
hr.ProcessServiceJson(result)
}
func (hr *HostReactor) asyncUpdateService() {
sema := util.NewSemaphore(hr.updateThreadNum)
for {
for _, v := range hr.serviceInfoMap.Items() {
service := v.(model.Service)
lastRefTime, ok := hr.updateTimeMap.Get(util.GetServiceCacheKey(service.Name, service.Clusters))
if !ok {
lastRefTime = uint64(0)
}
if uint64(util.CurrentMillis())-lastRefTime.(uint64) > service.CacheMillis {
sema.Acquire()
go func() {
hr.updateServiceNow(service.Name, service.Clusters)
sema.Release()
}()
}
}
time.Sleep(1 * time.Second)
}
}
// return true when service instance changed ,otherwise return false.
func isServiceInstanceChanged(oldService, newService *model.Service) bool {
oldHostsLen := len(oldService.Hosts)
newHostsLen := len(newService.Hosts)
if oldHostsLen != newHostsLen {
return true
}
// compare refTime
oldRefTime := oldService.LastRefTime
newRefTime := newService.LastRefTime
if oldRefTime > newRefTime {
logger.Warn(fmt.Sprintf("out of date data received, old-t: %v , new-t: %v", oldRefTime, newRefTime))
return false
}
// sort instance list
oldInstance := oldService.Hosts
newInstance := make([]model.Instance, len(newService.Hosts))
copy(newInstance, newService.Hosts)
sortInstance(oldInstance)
sortInstance(newInstance)
return !reflect.DeepEqual(oldInstance, newInstance)
}
type instanceSorter []model.Instance
func (s instanceSorter) Len() int {
return len(s)
}
func (s instanceSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s instanceSorter) Less(i, j int) bool {
insI, insJ := s[i], s[j]
// using ip and port to sort
ipNum1, _ := strconv.Atoi(strings.ReplaceAll(insI.Ip, ".", ""))
ipNum2, _ := strconv.Atoi(strings.ReplaceAll(insJ.Ip, ".", ""))
if ipNum1 < ipNum2 {
return true
}
if insI.Port < insJ.Port {
return true
}
return false
}
// sort instances
func sortInstance(instances []model.Instance) {
sort.Sort(instanceSorter(instances))
}

View File

@ -0,0 +1,186 @@
/*
* 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"
"os"
"reflect"
"sort"
"strconv"
"strings"
"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 cache.ConcurrentMap
updateCacheWhenEmpty bool
cacheDir string
notLoadCacheAtStart bool
subCallback *SubscribeCallback
UpdateTimeMap cache.ConcurrentMap
}
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: cache.NewConcurrentMap(),
ServiceInfoMap: cache.NewConcurrentMap(),
}
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.Set(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.Get(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.Set(cacheKey, uint64(util.CurrentMillis()))
s.ServiceInfoMap.Set(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)
}
monitor.GetServiceInfoMapSizeMonitor().Set(float64(s.ServiceInfoMap.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.Get(cacheKey)
if ok {
return service.(model.Service), ok
}
return model.Service{}, ok
}
func (s *ServiceInfoHolder) RegisterCallback(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
s.subCallback.AddCallbackFunc(serviceName, clusters, callbackFunc)
}
func (s *ServiceInfoHolder) DeregisterCallback(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
s.subCallback.RemoveCallbackFunc(serviceName, clusters, callbackFunc)
}
func (s *ServiceInfoHolder) StopUpdateIfContain(serviceName, clusters string) {
cacheKey := util.GetServiceCacheKey(serviceName, clusters)
s.ServiceInfoMap.Remove(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.Warn(fmt.Sprintf("out of date data received, old-t: %v , new-t: %v", oldRefTime, newRefTime))
return false
}
// sort instance list
oldInstance := oldService.Hosts
newInstance := make([]model.Instance, len(newService.Hosts))
copy(newInstance, newService.Hosts)
sortInstance(oldInstance)
sortInstance(newInstance)
return !reflect.DeepEqual(oldInstance, newInstance)
}
type instanceSorter []model.Instance
func (s instanceSorter) Len() int {
return len(s)
}
func (s instanceSorter) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s instanceSorter) Less(i, j int) bool {
insI, insJ := s[i], s[j]
// using ip and port to sort
ipNum1, _ := strconv.Atoi(strings.ReplaceAll(insI.Ip, ".", ""))
ipNum2, _ := strconv.Atoi(strings.ReplaceAll(insJ.Ip, ".", ""))
if ipNum1 < ipNum2 {
return true
}
if insI.Port < insJ.Port {
return true
}
return false
}
// sort instances
func sortInstance(instances []model.Instance) {
sort.Sort(instanceSorter(instances))
}

View File

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

View File

@ -0,0 +1,85 @@
/*
* 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/pkg/errors"
"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)
_, ok := ed.callbackFuncMap.Get(key)
return ok
}
func (ed *SubscribeCallback) AddCallbackFunc(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
key := util.GetServiceCacheKey(serviceName, clusters)
defer ed.mux.Unlock()
ed.mux.Lock()
var funcSlice []*func(services []model.Instance, err error)
old, ok := ed.callbackFuncMap.Get(key)
if ok {
funcSlice = append(funcSlice, old.([]*func(services []model.Instance, err error))...)
}
funcSlice = append(funcSlice, callbackFunc)
ed.callbackFuncMap.Set(key, funcSlice)
}
func (ed *SubscribeCallback) RemoveCallbackFunc(serviceName string, clusters string, callbackFunc *func(services []model.Instance, err error)) {
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 []*func(services []model.Instance, err error)
for _, funcItem := range funcs.([]*func(services []model.Instance, err error)) {
if funcItem != callbackFunc {
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.([]*func(services []model.Instance, err error)) {
if len(service.Hosts) == 0 {
(*funcItem)(service.Hosts, errors.New("[client.Subscribe] subscribe failed,hosts is empty"))
continue
}
(*funcItem)(service.Hosts, nil)
}
}
}

View File

@ -14,23 +14,23 @@
* limitations under the License.
*/
package naming_client
package naming_cache
import (
"fmt"
"log"
"strings"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/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{
Dom: "public@@Test",
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
@ -38,7 +38,6 @@ func TestEventDispatcher_AddCallbackFuncs(t *testing.T) {
}
var hosts []model.Instance
host := model.Instance{
Valid: true,
Enable: true,
InstanceId: "123",
Port: 8080,
@ -55,14 +54,15 @@ func TestEventDispatcher_AddCallbackFuncs(t *testing.T) {
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Println(util.ToJsonString(ed.callbackFuncMap))
},
}
ed.AddCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncsMap.Items() {
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*func(services []model.SubscribeService, err error))
funcs := v.([]*func(services []model.Instance, err error))
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0], &param.SubscribeCallback, "callback function must be equal!")
@ -71,7 +71,6 @@ func TestEventDispatcher_AddCallbackFuncs(t *testing.T) {
func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
service := model.Service{
Dom: "public@@Test",
Clusters: strings.Join([]string{"default"}, ","),
CacheMillis: 10000,
Checksum: "abcd",
@ -79,7 +78,6 @@ func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
}
var hosts []model.Instance
host := model.Instance{
Valid: true,
Enable: true,
InstanceId: "123",
Port: 8080,
@ -96,32 +94,34 @@ func TestEventDispatcher_RemoveCallbackFuncs(t *testing.T) {
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Printf("func1:%s \n", util.ToJsonString(services))
},
}
ed.AddCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
assert.Equal(t, len(ed.callbackFuncsMap.Items()), 1, "callback funcs map length should be 1")
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
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.SubscribeService, err error) {
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Printf("func2:%s \n", util.ToJsonString(services))
},
}
ed.AddCallbackFuncs(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
assert.Equal(t, len(ed.callbackFuncsMap.Items()), 1, "callback funcs map length should be 2")
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
assert.Equal(t, len(ed.callbackFuncMap.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))))
for k, v := range ed.callbackFuncMap.Items() {
log.Printf("key:%s,%d", k, len(v.([]*func(services []model.Instance, err error))))
}
ed.RemoveCallbackFuncs(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
ed.RemoveCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
key := util.GetServiceCacheKey(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
for k, v := range ed.callbackFuncsMap.Items() {
for k, v := range ed.callbackFuncMap.Items() {
assert.Equal(t, key, k, "key should be equal!")
funcs := v.([]*func(services []model.SubscribeService, err error))
funcs := v.([]*func(services []model.Instance, err error))
assert.Equal(t, len(funcs), 1)
assert.Equal(t, funcs[0], &param.SubscribeCallback, "callback function must be equal!")
@ -138,7 +138,6 @@ func TestSubscribeCallback_ServiceChanged(t *testing.T) {
}
var hosts []model.Instance
host := model.Instance{
Valid: true,
Enable: true,
InstanceId: "123",
Port: 8080,
@ -155,22 +154,22 @@ func TestSubscribeCallback_ServiceChanged(t *testing.T) {
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("func1:%s \n", util.ToJsonString(services))
},
}
ed.AddCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
param2 := vo.SubscribeParam{
ServiceName: "Test",
Clusters: []string{"default"},
GroupName: "public",
SubscribeCallback: func(services []model.SubscribeService, err error) {
SubscribeCallback: func(services []model.Instance, err error) {
log.Printf("func2:%s \n", util.ToJsonString(services))
},
}
ed.AddCallbackFuncs(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
ed.ServiceChanged(&service)
ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), &param2.SubscribeCallback)
cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters)
ed.ServiceChanged(cacheKey, &service)
}

View File

@ -19,81 +19,71 @@ package naming_client
import (
"math"
"math/rand"
"os"
"sort"
"strings"
"time"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_proxy"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// NamingClient ...
type NamingClient struct {
nacos_client.INacosClient
hostReactor HostReactor
serviceProxy NamingProxy
subCallback SubscribeCallback
beatReactor BeatReactor
indexMap cache.ConcurrentMap
NamespaceId string
serviceProxy naming_proxy.INamingProxy
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
type Chooser struct {
data []model.Instance
totals []int
max int
}
func NewNamingClient(nc nacos_client.INacosClient) (NamingClient, error) {
// NewNamingClient ...
func NewNamingClient(nc nacos_client.INacosClient) (*NamingClient, error) {
rand.Seed(time.Now().UnixNano())
naming := NamingClient{INacosClient: nc}
naming := &NamingClient{INacosClient: nc}
clientConfig, err := nc.GetClientConfig()
if err != nil {
return naming, err
}
naming.NamespaceId = clientConfig.NamespaceId
serverConfig, err := nc.GetServerConfig()
if err != nil {
return naming, err
}
httpAgent, err := nc.GetHttpAgent()
if err != nil {
return naming, err
}
loggerConfig := logger.Config{
LogFileName: constant.LOG_FILE_NAME,
Level: clientConfig.LogLevel,
Sampling: clientConfig.LogSampling,
LogRollingConfig: clientConfig.LogRollingConfig,
LogDir: clientConfig.LogDir,
CustomLogger: clientConfig.CustomLogger,
LogStdout: clientConfig.LogStdout,
if err = initLogger(clientConfig); err != nil {
return naming, err
}
err = logger.InitLogger(loggerConfig)
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 = NewNamingProxyDelegate(clientConfig, serverConfig, httpAgent, naming.serviceInfoHolder)
go NewServiceInfoUpdater(naming.serviceInfoHolder, clientConfig.UpdateThreadNum, naming.serviceProxy).asyncUpdateService()
if err != nil {
return naming, err
}
logger.GetLogger().Infof("logDir:<%s> cacheDir:<%s>", clientConfig.LogDir, clientConfig.CacheDir)
naming.subCallback = NewSubscribeCallback()
naming.serviceProxy, err = NewNamingProxy(clientConfig, serverConfig, httpAgent)
if err != nil {
return naming, err
}
naming.hostReactor = NewHostReactor(naming.serviceProxy, clientConfig.CacheDir+string(os.PathSeparator)+"naming",
clientConfig.UpdateThreadNum, clientConfig.NotLoadCacheAtStart, naming.subCallback, clientConfig.UpdateCacheWhenEmpty)
naming.beatReactor = NewBeatReactor(naming.serviceProxy, clientConfig.BeatInterval)
naming.indexMap = cache.NewConcurrentMap()
return naming, nil
}
//RegisterInstance register instance
func initLogger(clientConfig constant.ClientConfig) error {
return logger.InitLogger(logger.BuildLoggerConfig(clientConfig))
}
// RegisterInstance ...
func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool, error) {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
@ -114,126 +104,121 @@ func (sc *NamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
}
beatInfo := &model.BeatInfo{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ServiceName: util.GetGroupName(param.ServiceName, param.GroupName),
Cluster: param.ClusterName,
Weight: param.Weight,
Period: util.GetDurationWithDefault(param.Metadata, constant.HEART_BEAT_INTERVAL, time.Second*5),
State: model.StateRunning,
}
_, err := sc.serviceProxy.RegisterInstance(util.GetGroupName(param.ServiceName, param.GroupName), param.GroupName, instance)
if err != nil {
return false, err
}
if instance.Ephemeral {
sc.beatReactor.AddBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), beatInfo)
}
return true, nil
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
}
//DeregisterInstance deregister instance
// DeregisterInstance ...
func (sc *NamingClient) DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
sc.beatReactor.RemoveBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port)
_, err := sc.serviceProxy.DeregisterInstance(util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port, param.Cluster, param.Ephemeral)
if err != nil {
return false, err
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
ClusterName: param.Cluster,
Ephemeral: param.Ephemeral,
}
return true, nil
return sc.serviceProxy.DeregisterInstance(param.ServiceName, param.GroupName, instance)
}
//UpdateInstance update information for exist instance.
// UpdateInstance ...
func (sc *NamingClient) UpdateInstance(param vo.UpdateInstanceParam) (bool, error) {
if param.ServiceName == "" {
return false, errors.New("serviceName cannot be empty!")
}
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
if param.Ephemeral {
// Update the heartbeat information first to prevent the information
// from being flushed back to the original information after reconnecting
sc.beatReactor.RemoveBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port)
beatInfo := &model.BeatInfo{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ServiceName: util.GetGroupName(param.ServiceName, param.GroupName),
Cluster: param.ClusterName,
Weight: param.Weight,
Period: util.GetDurationWithDefault(param.Metadata, constant.HEART_BEAT_INTERVAL, time.Second*5),
State: model.StateRunning,
}
sc.beatReactor.AddBeatInfo(util.GetGroupName(param.ServiceName, param.GroupName), beatInfo)
if param.Metadata == nil {
param.Metadata = make(map[string]string)
}
instance := model.Instance{
Ip: param.Ip,
Port: param.Port,
Metadata: param.Metadata,
ClusterName: param.ClusterName,
Healthy: param.Healthy,
Enable: param.Enable,
Weight: param.Weight,
Ephemeral: param.Ephemeral,
}
// Do update instance
_, err := sc.serviceProxy.UpdateInstance(
util.GetGroupName(param.ServiceName, param.GroupName), param.Ip, param.Port, param.ClusterName, param.Ephemeral,
param.Weight, param.Enable, param.Metadata)
return sc.serviceProxy.RegisterInstance(param.ServiceName, param.GroupName, instance)
if err != nil {
return false, err
}
return true, nil
}
//GetService get service info
func (sc *NamingClient) GetService(param vo.GetServiceParam) (model.Service, error) {
// GetService Get service info by Group and DataId, clusters was optional
func (sc *NamingClient) GetService(param vo.GetServiceParam) (service model.Service, err error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
var ok bool
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
}
return service, err
}
//GetAllServicesInfo get all services info
// GetAllServicesInfo Get all instance by Namespace and Group with page
func (sc *NamingClient) GetAllServicesInfo(param vo.GetAllServiceInfoParam) (model.ServiceList, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
clientConfig, _ := sc.GetClientConfig()
if len(param.NameSpace) == 0 {
if len(sc.NamespaceId) == 0 {
if len(clientConfig.NamespaceId) == 0 {
param.NameSpace = constant.DEFAULT_NAMESPACE_ID
} else {
param.NameSpace = sc.NamespaceId
param.NameSpace = clientConfig.NamespaceId
}
}
if param.PageNo == 0 {
param.PageNo = 1
}
if param.PageSize == 0 {
param.PageSize = 10
}
services := sc.hostReactor.GetAllServiceInfo(param.NameSpace, param.GroupName, param.PageNo, param.PageSize)
return services, nil
services, err := sc.serviceProxy.GetServiceList(param.PageNo, param.PageSize, param.GroupName, &model.ExpressionSelector{})
return services, err
}
//SelectAllInstances select all instances
// SelectAllInstances Get all instance by DataId 和 Group
func (sc *NamingClient) SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
clusters := strings.Join(param.Clusters, ",")
var (
service model.Service
ok bool
err error
)
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
}
if err != nil || service.Hosts == nil || len(service.Hosts) == 0 {
return []model.Instance{}, err
}
return service.Hosts, err
}
//SelectInstances select instances
// SelectInstances Get all instance by DataId, Group and Health
func (sc *NamingClient) SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
if err != nil {
return nil, err
var (
service model.Service
ok bool
err error
)
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
if err != nil {
return nil, err
}
}
return sc.selectInstances(service, param.HealthyOnly)
}
@ -252,15 +237,25 @@ func (sc *NamingClient) selectInstances(service model.Service, healthy bool) ([]
return result, nil
}
//SelectOneHealthyInstance select one healthy instance
// SelectOneHealthyInstance Get one healthy instance by DataId and Group
func (sc *NamingClient) SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error) {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
service, err := sc.hostReactor.GetServiceInfo(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","))
if err != nil {
return nil, err
var (
service model.Service
ok bool
err error
)
clusters := strings.Join(param.Clusters, ",")
service, ok = sc.serviceInfoHolder.GetServiceInfo(param.ServiceName, param.GroupName, clusters)
if !ok {
service, err = sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
if err != nil {
return nil, err
}
}
return sc.selectOneHealthyInstances(service)
}
@ -284,67 +279,34 @@ func (sc *NamingClient) selectOneHealthyInstances(service model.Service) (*model
return nil, errors.New("healthy instance list is empty!")
}
chooser := newChooser(result)
instance := chooser.pick()
instance := newChooser(result).pick()
return &instance, nil
}
type instance []model.Instance
func (a instance) Len() int {
return len(a)
}
func (a instance) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a instance) Less(i, j int) bool {
return a[i].Weight < a[j].Weight
}
// NewChooser initializes a new Chooser for picking from the provided Choices.
func newChooser(instances []model.Instance) Chooser {
sort.Sort(instance(instances))
totals := make([]int, len(instances))
runningTotal := 0
for i, c := range instances {
runningTotal += int(c.Weight)
totals[i] = runningTotal
}
return Chooser{data: instances, totals: totals, max: runningTotal}
}
func (chs Chooser) pick() model.Instance {
r := rand.Intn(chs.max) + 1
i := sort.SearchInts(chs.totals, r)
return chs.data[i]
}
//Subscribe subscibe service
// Subscribe ...
func (sc *NamingClient) Subscribe(param *vo.SubscribeParam) error {
if len(param.GroupName) == 0 {
param.GroupName = constant.DEFAULT_GROUP
}
serviceParam := vo.GetServiceParam{
ServiceName: param.ServiceName,
GroupName: param.GroupName,
Clusters: param.Clusters,
}
sc.subCallback.AddCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
svc, err := sc.GetService(serviceParam)
if err != nil {
return err
}
if !sc.hostReactor.serviceProxy.clientConfig.NotLoadCacheAtStart {
sc.subCallback.ServiceChanged(&svc)
}
return nil
clusters := strings.Join(param.Clusters, ",")
sc.serviceInfoHolder.RegisterCallback(util.GetGroupName(param.ServiceName, param.GroupName), clusters, &param.SubscribeCallback)
_, err := sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters)
return err
}
//Unsubscribe unsubscribe service
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) error {
sc.subCallback.RemoveCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
return nil
// Unsubscribe ...
func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) (err error) {
clusters := strings.Join(param.Clusters, ",")
serviceFullName := util.GetGroupName(param.ServiceName, param.GroupName)
sc.serviceInfoHolder.DeregisterCallback(serviceFullName, clusters, &param.SubscribeCallback)
if sc.serviceInfoHolder.IsSubscribed(serviceFullName, clusters) {
err = sc.serviceProxy.Unsubscribe(param.ServiceName, param.GroupName, clusters)
}
return err
}
// CloseClient ...
func (sc *NamingClient) CloseClient() {
sc.serviceProxy.CloseClient()
}

View File

@ -13,42 +13,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
//go:generate mockgen -destination ../../mock/mock_service_client_interface.go -package mock -source=./service_client_interface.go
// INamingClient interface for naming client
type INamingClient interface {
//RegisterInstance use to register instance
//Ip require
//Port require
//Tenant optional
//Weight require,it must be lager than 0
//Enable require,the instance can be access or not
//Healthy require,the instance is health or not
//Metadata optional
//ClusterName optional,default:DEFAULT
//ServiceName require
//GroupName optional,default:DEFAULT_GROUP
//Ephemeral optional
// RegisterInstance use to register instance
// Ip require
// Port require
// Weight require,it must be lager than 0
// Enable require,the instance can be access or not
// Healthy require,the instance is health or not
// Metadata optional
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
RegisterInstance(param vo.RegisterInstanceParam) (bool, error)
//DeregisterInstance use to deregister instance
//Ip required
//Port required
//Tenant optional
//Cluster optional,default:DEFAULT
//ServiceName require
//GroupName optional,default:DEFAULT_GROUP
//Ephemeral optional
DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error)
// UpdateInstance use to modify instance
// DeregisterInstance use to deregister instance
// Ip required
// Port required
// Tenant optional
@ -56,51 +47,64 @@ type INamingClient interface {
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error)
// UpdateInstance use to update instance
// Ip require
// Port require
// Weight require,it must be lager than 0
// Enable require,the instance can be access or not
// Healthy require,the instance is health or not
// Metadata optional
// ClusterName optional,default:DEFAULT
// ServiceName require
// GroupName optional,default:DEFAULT_GROUP
// Ephemeral optional
UpdateInstance(param vo.UpdateInstanceParam) (bool, error)
//GetService use to get service
//ServiceName require
//Clusters optional,default:DEFAULT
//GroupName optional,default:DEFAULT_GROUP
// GetService use to get service
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
GetService(param vo.GetServiceParam) (model.Service, error)
//SelectAllInstance return all instances,include healthy=false,enable=false,weight<=0
//ServiceName require
//Clusters optional,default:DEFAULT
//GroupName optional,default:DEFAULT_GROUP
// SelectAllInstances return all instances,include healthy=false,enable=false,weight<=0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error)
//SelectInstances only return the instances of healthy=${HealthyOnly},enable=true and weight>0
//ServiceName require
//Clusters optional,default:DEFAULT
//GroupName optional,default:DEFAULT_GROUP
//HealthyOnly optional
// SelectInstances only return the instances of healthy=${HealthyOnly},enable=true and weight>0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// HealthyOnly optional
SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error)
//SelectInstances return one instance by WRR strategy for load balance
//And the instance should be health=true,enable=true and weight>0
//ServiceName require
//Clusters optional,default:DEFAULT
//GroupName optional,default:DEFAULT_GROUP
// SelectOneHealthyInstance return one instance by WRR strategy for load balance
// And the instance should be health=true,enable=true and weight>0
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error)
//Subscribe use to subscribe service change event
//ServiceName require
//Clusters optional,default:DEFAULT
//GroupName optional,default:DEFAULT_GROUP
//SubscribeCallback require
// Subscribe use to subscribe service change event
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// SubscribeCallback require
Subscribe(param *vo.SubscribeParam) error
//Unsubscribe use to unsubscribe service change event
//ServiceName require
//Clusters optional,default:DEFAULT
//GroupName optional,default:DEFAULT_GROUP
//SubscribeCallback require
// Unsubscribe use to unsubscribe service change event
// ServiceName require
// Clusters optional,default:DEFAULT
// GroupName optional,default:DEFAULT_GROUP
// SubscribeCallback require
Unsubscribe(param *vo.SubscribeParam) error
//GetAllServicesInfo use to get all service info by page
// GetAllServicesInfo use to get all service info by page
GetAllServicesInfo(param vo.GetAllServiceInfoParam) (model.ServiceList, error)
//CloseClient close the GRPC client
CloseClient()
}

View File

@ -17,18 +17,15 @@
package naming_client
import (
"net/http"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/mock"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/clients/nacos_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/stretchr/testify/assert"
)
var clientConfigTest = *constant.NewClientConfig(
@ -37,39 +34,52 @@ var clientConfigTest = *constant.NewClientConfig(
constant.WithNotLoadCacheAtStart(true),
)
var serverConfigTest = *constant.NewServerConfig("console.nacos.io", 80, constant.WithContextPath("/nacos"))
var serverConfigTest = *constant.NewServerConfig("127.0.0.1", 80, constant.WithContextPath("/nacos"))
func Test_RegisterServiceInstance_withoutGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "DEFAULT_GROUP@@DEMO",
"groupName": "DEFAULT_GROUP",
"app": "",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
type MockNamingProxy struct {
}
func (m *MockNamingProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return true, nil
}
func (m *MockNamingProxy) DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error) {
return true, nil
}
func (m *MockNamingProxy) GetServiceList(pageNo uint32, pageSize uint32, groupName 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 {
return nil
}
func (m *MockNamingProxy) CloseClient() {}
func NewTestNamingClient() *NamingClient {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewNamingClient(&nc)
success, err := client.RegisterInstance(vo.RegisterInstanceParam{
client.serviceProxy = &MockNamingProxy{}
return client
}
func Test_RegisterServiceInstance_withoutGroupName(t *testing.T) {
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
@ -80,39 +90,8 @@ func Test_RegisterServiceInstance_withoutGroupName(t *testing.T) {
}
func Test_RegisterServiceInstance_withGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO2",
"groupName": "test_group",
"app": "",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
success, err := client.RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO2",
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
@ -123,39 +102,8 @@ func Test_RegisterServiceInstance_withGroupName(t *testing.T) {
}
func Test_RegisterServiceInstance_withCluster(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO3",
"groupName": "test_group",
"app": "",
"clusterName": "test",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
success, err := client.RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO3",
success, err := NewTestNamingClient().RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
@ -165,198 +113,35 @@ func Test_RegisterServiceInstance_withCluster(t *testing.T) {
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func Test_RegisterServiceInstance_401(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("POST"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO4",
"groupName": "test_group",
"app": "",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"healthy": "false",
"metadata": "{}",
"ephemeral": "false",
})).Times(3).
Return(http_agent.FakeHttpResponse(401, `no security`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
result, err := client.RegisterInstance(vo.RegisterInstanceParam{
ServiceName: "DEMO4",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: false,
})
assert.Equal(t, false, result)
assert.NotNil(t, err)
}
func TestNamingProxy_DeregisterService_WithoutGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("DELETE"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "DEFAULT_GROUP@@DEMO5",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"ephemeral": "true",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
_, _ = client.DeregisterInstance(vo.DeregisterInstanceParam{
success, err := NewTestNamingClient().DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO5",
Ip: "10.0.0.10",
Port: 80,
Ephemeral: true,
})
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func TestNamingProxy_DeregisterService_WithGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("DELETE"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO6",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"ephemeral": "true",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
_, _ = client.DeregisterInstance(vo.DeregisterInstanceParam{
success, err := NewTestNamingClient().DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO6",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: true,
})
}
func Test_UpdateServiceInstance_withoutGroupName(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("PUT"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "DEFAULT_GROUP@@DEMO",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"weight": "0",
"enable": "false",
"metadata": "{}",
"ephemeral": "false",
})).Times(1).
Return(http_agent.FakeHttpResponse(200, `ok`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
success, err := client.UpdateInstance(vo.UpdateInstanceParam{
ServiceName: "DEMO",
Ip: "10.0.0.10",
Port: 80,
Ephemeral: false,
Metadata: map[string]string{},
})
assert.Equal(t, nil, err)
assert.Equal(t, true, success)
}
func TestNamingProxy_DeregisterService_401(t *testing.T) {
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
mockIHttpAgent.EXPECT().Request(gomock.Eq("DELETE"),
gomock.Eq("http://console.nacos.io:80/nacos/v1/ns/instance"),
gomock.AssignableToTypeOf(http.Header{}),
gomock.Eq(uint64(10*1000)),
gomock.Eq(map[string]string{
"namespaceId": "",
"serviceName": "test_group@@DEMO7",
"clusterName": "",
"ip": "10.0.0.10",
"port": "80",
"ephemeral": "true",
})).Times(3).
Return(http_agent.FakeHttpResponse(401, `no security`), nil)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
_, _ = client.DeregisterInstance(vo.DeregisterInstanceParam{
ServiceName: "DEMO7",
Ip: "10.0.0.10",
Port: 80,
GroupName: "test_group",
Ephemeral: true,
})
}
func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
@ -368,8 +153,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
@ -381,8 +164,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
@ -394,8 +175,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: false,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
@ -407,8 +186,6 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
@ -421,61 +198,33 @@ func TestNamingClient_SelectOneHealthyInstance_SameWeight(t *testing.T) {
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instance1, err := client.selectOneHealthyInstances(services)
LastRefTime: 1528787794594, Clusters: "a"}
instance1, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.Nil(t, err)
assert.NotNil(t, instance1)
instance2, err := client.selectOneHealthyInstances(services)
instance2, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.Nil(t, err)
assert.NotNil(t, instance2)
}
func TestNamingClient_SelectOneHealthyInstance_Empty(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instance, err := client.selectOneHealthyInstances(services)
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Clusters: "a"}
instance, err := NewTestNamingClient().selectOneHealthyInstances(services)
assert.NotNil(t, err)
assert.Nil(t, instance)
}
func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
@ -487,8 +236,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
@ -500,8 +247,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
@ -513,8 +258,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: false,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
@ -526,8 +269,6 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
@ -540,33 +281,18 @@ func TestNamingClient_SelectInstances_Healthy(t *testing.T) {
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instances, err := client.selectInstances(services, true)
LastRefTime: 1528787794594, Clusters: "a"}
instances, err := NewTestNamingClient().selectInstances(services, true)
assert.Nil(t, err)
assert.Equal(t, 2, len(instances))
}
func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
@ -578,8 +304,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
@ -591,8 +315,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
@ -604,8 +326,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: false,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
@ -617,8 +337,6 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
Healthy: true,
},
{
Valid: true,
Marked: false,
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
@ -631,87 +349,102 @@ func TestNamingClient_SelectInstances_Unhealthy(t *testing.T) {
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instances, err := client.selectInstances(services, false)
LastRefTime: 1528787794594, Clusters: "a"}
instances, err := NewTestNamingClient().selectInstances(services, false)
assert.Nil(t, err)
assert.Equal(t, 1, len(instances))
}
func TestNamingClient_SelectInstances_Empty(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
UseSpecifiedURL: false,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Env: "", Clusters: "a",
Metadata: map[string]string(nil)})
ctrl := gomock.NewController(t)
defer func() {
ctrl.Finish()
}()
mockIHttpAgent := mock.NewMockIHttpAgent(ctrl)
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(mockIHttpAgent)
client, _ := NewNamingClient(&nc)
instances, err := client.selectInstances(services, false)
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Clusters: "a"}
instances, err := NewTestNamingClient().selectInstances(services, false)
assert.NotNil(t, err)
assert.Equal(t, 0, len(instances))
}
func TestNamingClient_GetAllServicesInfo(t *testing.T) {
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
_ = nc.SetHttpAgent(&http_agent.HttpAgent{})
client, _ := NewNamingClient(&nc)
reslut, err := client.GetAllServicesInfo(vo.GetAllServiceInfoParam{
result, err := NewTestNamingClient().GetAllServicesInfo(vo.GetAllServiceInfoParam{
GroupName: "DEFAULT_GROUP",
PageNo: 1,
PageSize: 20,
})
assert.NotNil(t, reslut.Doms)
assert.NotNil(t, result.Doms)
assert.Nil(t, err)
}
func TestNamingClient_selectOneHealthyInstanceResult(t *testing.T) {
services := model.Service(model.Service{
Name: "DEFAULT_GROUP@@DEMO",
func BenchmarkNamingClient_SelectOneHealthyInstances(b *testing.B) {
services := model.Service{
Name: "DEFAULT_GROUP@@DEMO",
CacheMillis: 1000,
Hosts: []model.Instance{
{
Ip: "127.0.0.1",
Weight: 1,
Enable: true,
Healthy: true,
InstanceId: "10.10.10.10-80-a-DEMO",
Port: 80,
Ip: "10.10.10.10",
Weight: 10,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO1",
Enable: true,
Healthy: true,
},
{
Ip: "127.0.0.2",
Weight: 9,
Enable: true,
Healthy: true,
InstanceId: "10.10.10.11-80-a-DEMO",
Port: 80,
Ip: "10.10.10.11",
Weight: 10,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO2",
Enable: true,
Healthy: true,
},
}})
nc := nacos_client.NacosClient{}
_ = nc.SetServerConfig([]constant.ServerConfig{serverConfigTest})
_ = nc.SetClientConfig(clientConfigTest)
client, _ := NewNamingClient(&nc)
for i := 0; i < 10; i++ {
{
InstanceId: "10.10.10.12-80-a-DEMO",
Port: 80,
Ip: "10.10.10.12",
Weight: 1,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO3",
Enable: true,
Healthy: false,
},
{
InstanceId: "10.10.10.13-80-a-DEMO",
Port: 80,
Ip: "10.10.10.13",
Weight: 1,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO4",
Enable: false,
Healthy: true,
},
{
InstanceId: "10.10.10.14-80-a-DEMO",
Port: 80,
Ip: "10.10.10.14",
Weight: 0,
Metadata: map[string]string{},
ClusterName: "a",
ServiceName: "DEMO5",
Enable: true,
Healthy: true,
},
},
Checksum: "3bbcf6dd1175203a8afdade0e77a27cd1528787794594",
LastRefTime: 1528787794594, Clusters: "a"}
client := NewTestNamingClient()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = client.selectOneHealthyInstances(services)
}
}

View File

@ -0,0 +1,123 @@
/*
* 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 (
"reflect"
"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() {
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)
return
}
grpcProxy, ok := c.clientProxy.(*NamingGrpcProxy)
if !ok {
return
}
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]
instance, ok := v.(model.Instance)
if !ok {
logger.Warnf("redo register service:%s faild,instances type not is model.instance", info[1])
return
}
_, err := c.clientProxy.RegisterInstance(serviceName, groupName, instance)
if err != nil {
logger.Warnf("redo register service:%s groupName:%s faild:%s", info[1], info[0], err.Error())
}
}
}
func (c *ConnectionEventListener) CacheInstanceForRedo(serviceName, groupName string, instance model.Instance) {
key := util.GetGroupName(serviceName, groupName)
getInstance, _ := c.registeredInstanceCached.Get(key)
if getInstance != nil && reflect.DeepEqual(getInstance.(model.Instance), instance) {
return
}
c.registeredInstanceCached.Set(key, instance)
}
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 _, ok := c.subscribes.Get(key); !ok {
c.subscribes.Set(key, struct{}{})
}
}
func (c *ConnectionEventListener) RemoveSubscriberForRedo(fullServiceName, clusters string) {
c.subscribes.Remove(util.GetServiceCacheKey(fullServiceName, clusters))
}

View File

@ -0,0 +1,34 @@
package naming_grpc
import (
"testing"
"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"
)
func TestRedoSubscribe(t *testing.T) {
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,181 @@
/*
* 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 (
"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/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(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(uid.String(), rpc.GRPC, labels, srvProxy.nacosServer)
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.InjectSign(request, request.GetHeaders(), proxy.clientConfig)
proxy.nacosServer.InjectSecurityInfo(request.GetHeaders())
response, err := proxy.rpcClient.GetRpcClient().Request(request, int64(proxy.clientConfig.TimeoutMs))
monitor.GetConfigRequestMonitor(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("instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instance))
instanceRequest := rpc_request.NewInstanceRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, "registerInstance", instance)
response, err := proxy.requestToServer(instanceRequest)
proxy.eventListener.CacheInstanceForRedo(serviceName, groupName, instance)
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 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(proxy.clientConfig.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, clusters string, udpPort int, healthyOnly bool) (*model.Service, error) {
response, err := proxy.requestToServer(rpc_request.NewServiceQueryRequest(proxy.clientConfig.NamespaceId, serviceName, groupName, clusters,
healthyOnly, udpPort))
if err != nil {
return nil, err
}
queryServiceResponse := response.(*rpc_response.QueryServiceResponse)
return &queryServiceResponse.ServiceInfo, nil
}
// Subscribe ...
func (proxy *NamingGrpcProxy) Subscribe(serviceName, groupName string, clusters string) (model.Service, error) {
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 {
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() {
proxy.rpcClient.GetRpcClient().Shutdown()
}

View File

@ -0,0 +1,32 @@
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) 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) {}

View File

@ -14,30 +14,38 @@
* limitations under the License.
*/
package naming_client
package naming_http
import (
"context"
"fmt"
"net/http"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/buger/jsonparser"
"github.com/nacos-group/nacos-sdk-go/v2/clients/cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"golang.org/x/sync/semaphore"
)
type BeatReactor struct {
beatMap cache.ConcurrentMap
serviceProxy NamingProxy
clientBeatInterval int64
nacosServer *nacos_server.NacosServer
beatThreadCount int
beatThreadSemaphore *semaphore.Weighted
beatRecordMap cache.ConcurrentMap
clientCfg constant.ClientConfig
mux *sync.Mutex
}
@ -45,14 +53,11 @@ const DefaultBeatThreadNum = 20
var ctx = context.Background()
func NewBeatReactor(serviceProxy NamingProxy, clientBeatInterval int64) BeatReactor {
func NewBeatReactor(clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer) BeatReactor {
br := BeatReactor{}
if clientBeatInterval <= 0 {
clientBeatInterval = 5 * 1000
}
br.beatMap = cache.NewConcurrentMap()
br.serviceProxy = serviceProxy
br.clientBeatInterval = clientBeatInterval
br.nacosServer = nacosServer
br.clientCfg = clientCfg
br.beatThreadCount = DefaultBeatThreadNum
br.beatRecordMap = cache.NewConcurrentMap()
br.beatThreadSemaphore = semaphore.NewWeighted(int64(br.beatThreadCount))
@ -76,6 +81,7 @@ func (br *BeatReactor) AddBeatInfo(serviceName string, beatInfo *model.BeatInfo)
}
br.beatMap.Set(k, beatInfo)
beatInfo.Metadata = util.DeepCopyMap(beatInfo.Metadata)
monitor.GetDom2BeatSizeMonitor().Set(float64(br.beatMap.Count()))
go br.sendInstanceBeat(k, beatInfo)
}
@ -89,16 +95,14 @@ func (br *BeatReactor) RemoveBeatInfo(serviceName string, ip string, port uint64
beatInfo := data.(*model.BeatInfo)
atomic.StoreInt32(&beatInfo.State, int32(model.StateShutdown))
}
monitor.GetDom2BeatSizeMonitor().Set(float64(br.beatMap.Count()))
br.beatMap.Remove(k)
}
func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
for {
err := br.beatThreadSemaphore.Acquire(ctx, 1)
if err != nil {
logger.Errorf("sendInstanceBeat failed to acquire semaphore: %v", err)
return
}
br.beatThreadSemaphore.Acquire(ctx, 1)
//如果当前实例注销,则进行停止心跳
if atomic.LoadInt32(&beatInfo.State) == int32(model.StateShutdown) {
logger.Infof("instance[%s] stop heartBeating", k)
@ -107,7 +111,7 @@ func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
}
//进行心跳通信
beatInterval, err := br.serviceProxy.SendBeat(beatInfo)
beatInterval, err := br.SendBeat(beatInfo)
if err != nil {
logger.Errorf("beat to server return error:%+v", err)
br.beatThreadSemaphore.Release(1)
@ -126,3 +130,26 @@ func (br *BeatReactor) sendInstanceBeat(k string, beatInfo *model.BeatInfo) {
<-t.C
}
}
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)
if err != nil {
return 0, err
}
if result != "" {
interVal, err := jsonparser.GetInt([]byte(result), "clientBeatInterval")
if err != nil {
return 0, errors.New(fmt.Sprintf("namespaceId:<%s> sending beat to server:<%s> get 'clientBeatInterval' from <%s> error:<%+v>", br.clientCfg.NamespaceId, util.ToJsonString(info), result, err))
} else {
return interVal, nil
}
}
return 0, nil
}

View File

@ -14,21 +14,20 @@
* limitations under the License.
*/
package naming_client
package naming_http
import (
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/v2/model"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/stretchr/testify/assert"
)
func TestBeatReactor_AddBeatInfo(t *testing.T) {
br := NewBeatReactor(NamingProxy{nacosServer: &nacos_server.NacosServer{}}, 5000)
br := NewBeatReactor(constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo := &model.BeatInfo{
@ -38,7 +37,6 @@ func TestBeatReactor_AddBeatInfo(t *testing.T) {
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
Period: time.Second * 5,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo)
key := buildKey(util.GetGroupName(serviceName, groupName), beatInfo.Ip, beatInfo.Port)
@ -48,7 +46,7 @@ func TestBeatReactor_AddBeatInfo(t *testing.T) {
}
func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
br := NewBeatReactor(NamingProxy{nacosServer: &nacos_server.NacosServer{}}, 5000)
br := NewBeatReactor(constant.ClientConfig{}, &nacos_server.NacosServer{})
serviceName := "Test"
groupName := "public"
beatInfo1 := &model.BeatInfo{
@ -58,7 +56,6 @@ func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
Period: time.Second * 5,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo1)
beatInfo2 := &model.BeatInfo{
@ -68,7 +65,6 @@ func TestBeatReactor_RemoveBeatInfo(t *testing.T) {
ServiceName: util.GetGroupName(serviceName, groupName),
Cluster: "default",
Weight: 1,
Period: time.Second * 5,
}
br.AddBeatInfo(util.GetGroupName(serviceName, groupName), beatInfo2)
br.RemoveBeatInfo(util.GetGroupName(serviceName, groupName), "127.0.0.1", 8080)

View File

@ -0,0 +1,214 @@
/*
* 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 (
"fmt"
"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(clientCfg constant.ClientConfig, nacosServer *nacos_server.NacosServer,
serviceInfoHolder *naming_cache.ServiceInfoHolder) (*NamingHttpProxy, error) {
srvProxy := NamingHttpProxy{
clientConfig: clientCfg,
nacosServer: nacosServer,
serviceInfoHolder: serviceInfoHolder,
}
srvProxy.beatReactor = NewBeatReactor(clientCfg, nacosServer)
NewPushReceiver(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)
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
}
// 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)
if err != nil {
return false, err
}
return true, nil
}
// GetServiceList ...
func (proxy *NamingHttpProxy) GetServiceList(pageNo uint32, pageSize uint32, groupName string, selector *model.ExpressionSelector) (model.ServiceList, error) {
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.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)
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.New(fmt.Sprintf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'count' from <%s> error:<%+v>", proxy.clientConfig.NamespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err))
}
var doms []string
_, err = jsonparser.ArrayEach([]byte(result), func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
doms = append(doms, string(value))
}, "doms")
if err != nil {
return serviceList, errors.New(fmt.Sprintf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'doms' from <%s> error:<%+v> ", proxy.clientConfig.NamespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err))
}
serviceList.Count = count
serviceList.Doms = doms
return serviceList, nil
}
// ServerHealthy ...
func (proxy *NamingHttpProxy) ServerHealthy() bool {
api := constant.SERVICE_BASE_PATH + "/operator/metrics"
result, err := proxy.nacosServer.ReqApi(api, map[string]string{}, http.MethodGet)
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
return false
}
if result != "" {
status, err := jsonparser.GetString([]byte(result), "status")
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
} else {
return status == "UP"
}
}
return false
}
// 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)
if err != nil {
return nil, err
}
return util.JsonToService(result), nil
}
// Subscribe ...
func (proxy *NamingHttpProxy) Subscribe(serviceName, groupName, clusters string) (model.Service, error) {
return model.Service{}, nil
}
// Unsubscribe ...
func (proxy *NamingHttpProxy) Unsubscribe(serviceName, groupName, clusters string) error {
return nil
}
func (proxy *NamingHttpProxy) CloseClient() {
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package naming_client
package naming_http
import (
"bytes"
@ -26,14 +26,15 @@ import (
"strconv"
"time"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type PushReceiver struct {
port int
host string
hostReactor *HostReactor
port int
host string
serviceInfoHolder *naming_cache.ServiceInfoHolder
}
type PushData struct {
@ -46,11 +47,10 @@ var (
GZIP_MAGIC = []byte("\x1F\x8B")
)
func NewPushReceiver(hostReactor *HostReactor) *PushReceiver {
func NewPushReceiver(serviceInfoHolder *naming_cache.ServiceInfoHolder) *PushReceiver {
pr := PushReceiver{
hostReactor: hostReactor,
serviceInfoHolder: serviceInfoHolder,
}
pr.startServer()
return &pr
}
@ -70,29 +70,32 @@ func (us *PushReceiver) tryListen() (*net.UDPConn, bool) {
return conn, true
}
func (us *PushReceiver) getConn() *net.UDPConn {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
func (us *PushReceiver) startServer() {
var (
conn *net.UDPConn
ok bool
)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 3; i++ {
port := r.Intn(1000) + 54951
us.port = port
conn, ok := us.tryListen()
conn, ok = us.tryListen()
if ok {
logger.Infof("udp server start, port: " + strconv.Itoa(port))
return conn
break
}
if !ok && i == 2 {
logger.Errorf("failed to start udp server after trying 3 times.")
}
}
return nil
}
func (us *PushReceiver) startServer() {
conn := us.getConn()
if conn == nil {
return
}
go func() {
defer conn.Close()
for {
@ -102,15 +105,6 @@ func (us *PushReceiver) startServer() {
}
func (us *PushReceiver) handleClient(conn *net.UDPConn) {
if conn == nil {
time.Sleep(time.Second * 5)
conn = us.getConn()
if conn == nil {
return
}
}
data := make([]byte, 4024)
n, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
@ -130,7 +124,7 @@ func (us *PushReceiver) handleClient(conn *net.UDPConn) {
ack := make(map[string]string)
if pushData.PushType == "dom" || pushData.PushType == "service" {
us.hostReactor.ProcessServiceJson(pushData.Data)
us.serviceInfoHolder.ProcessServiceJson(pushData.Data)
ack["type"] = "push-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
@ -139,7 +133,7 @@ func (us *PushReceiver) handleClient(conn *net.UDPConn) {
} else if pushData.PushType == "dump" {
ack["type"] = "dump-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)
ack["data"] = util.ToJsonString(us.hostReactor.serviceInfoMap)
ack["data"] = util.ToJsonString(us.serviceInfoHolder.ServiceInfoMap)
} else {
ack["type"] = "unknow-ack"
ack["lastRefTime"] = strconv.FormatInt(pushData.LastRefTime, 10)

View File

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

View File

@ -1,205 +0,0 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/buger/jsonparser"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/common/nacos_server"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
)
type NamingProxy struct {
clientConfig constant.ClientConfig
nacosServer *nacos_server.NacosServer
}
func NewNamingProxy(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig, httpAgent http_agent.IHttpAgent) (NamingProxy, error) {
srvProxy := NamingProxy{}
srvProxy.clientConfig = clientCfg
var err error
srvProxy.nacosServer, err = nacos_server.NewNacosServer(serverCfgs, clientCfg, httpAgent, clientCfg.TimeoutMs, clientCfg.Endpoint)
if err != nil {
return srvProxy, err
}
return srvProxy, nil
}
func (proxy *NamingProxy) RegisterInstance(serviceName string, groupName string, instance model.Instance) (string, error) {
logger.Infof("register instance namespaceId:<%s>,serviceName:<%s> with instance:<%s>",
proxy.clientConfig.NamespaceId, serviceName, util.ToJsonString(instance))
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["groupName"] = groupName
params["app"] = proxy.clientConfig.AppName
params["clusterName"] = instance.ClusterName
params["ip"] = instance.Ip
params["port"] = strconv.Itoa(int(instance.Port))
params["weight"] = strconv.FormatFloat(instance.Weight, 'f', -1, 64)
params["enable"] = strconv.FormatBool(instance.Enable)
params["healthy"] = strconv.FormatBool(instance.Healthy)
params["metadata"] = util.ToJsonString(instance.Metadata)
params["ephemeral"] = strconv.FormatBool(instance.Ephemeral)
return proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodPost)
}
func (proxy *NamingProxy) DeregisterInstance(serviceName string, ip string, port uint64, clusterName string, ephemeral bool) (string, error) {
logger.Infof("deregister instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
proxy.clientConfig.NamespaceId, serviceName, ip, port, clusterName)
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["clusterName"] = clusterName
params["ip"] = ip
params["port"] = strconv.Itoa(int(port))
params["ephemeral"] = strconv.FormatBool(ephemeral)
return proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodDelete)
}
func (proxy *NamingProxy) UpdateInstance(serviceName string, ip string, port uint64, clusterName string, ephemeral bool, weight float64, enable bool, metadata map[string]string) (string, error) {
logger.Infof("modify instance namespaceId:<%s>,serviceName:<%s> with instance:<%s:%d@%s>",
proxy.clientConfig.NamespaceId, serviceName, ip, port, clusterName)
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = serviceName
params["clusterName"] = clusterName
params["ip"] = ip
params["port"] = strconv.Itoa(int(port))
params["ephemeral"] = strconv.FormatBool(ephemeral)
params["weight"] = strconv.FormatFloat(weight, 'f', -1, 64)
params["enable"] = strconv.FormatBool(enable)
params["metadata"] = util.ToJsonString(metadata)
return proxy.nacosServer.ReqApi(constant.SERVICE_PATH, params, http.MethodPut)
}
func (proxy *NamingProxy) SendBeat(info *model.BeatInfo) (int64, error) {
logger.Infof("namespaceId:<%s> sending beat to server:<%s>",
proxy.clientConfig.NamespaceId, util.ToJsonString(info))
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["serviceName"] = info.ServiceName
params["beat"] = util.ToJsonString(info)
api := constant.SERVICE_BASE_PATH + "/instance/beat"
result, err := proxy.nacosServer.ReqApi(api, params, http.MethodPut)
if err != nil {
return 0, err
}
if result != "" {
interVal, err := jsonparser.GetInt([]byte(result), "clientBeatInterval")
if err != nil {
return 0, fmt.Errorf("namespaceId:<%s> sending beat to server:<%s> get 'clientBeatInterval' from <%s> error:<%+v>", proxy.clientConfig.NamespaceId, util.ToJsonString(info), result, err)
} else {
return interVal, nil
}
}
return 0, nil
}
func (proxy *NamingProxy) GetServiceList(pageNo int, pageSize int, groupName string, selector *model.ExpressionSelector) (*model.ServiceList, error) {
params := map[string]string{}
params["namespaceId"] = proxy.clientConfig.NamespaceId
params["groupName"] = groupName
params["pageNo"] = strconv.Itoa(pageNo)
params["pageSize"] = strconv.Itoa(pageSize)
if selector != nil {
switch selector.Type {
case "label":
params["selector"] = util.ToJsonString(selector)
}
}
api := constant.SERVICE_BASE_PATH + "/service/list"
result, err := proxy.nacosServer.ReqApi(api, params, http.MethodGet)
if err != nil {
return nil, err
}
if result == "" {
return nil, errors.New("request server return empty")
}
serviceList := model.ServiceList{}
count, err := jsonparser.GetInt([]byte(result), "count")
if err != nil {
return nil, fmt.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'count' from <%s> error:<%+v>", proxy.clientConfig.NamespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err)
}
var doms []string
_, err = jsonparser.ArrayEach([]byte(result), func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
doms = append(doms, string(value))
}, "doms")
if err != nil {
return nil, fmt.Errorf("namespaceId:<%s> get service list pageNo:<%d> pageSize:<%d> selector:<%s> from <%s> get 'doms' from <%s> error:<%+v> ", proxy.clientConfig.NamespaceId, pageNo, pageSize, util.ToJsonString(selector), groupName, result, err)
}
serviceList.Count = count
serviceList.Doms = doms
return &serviceList, nil
}
func (proxy *NamingProxy) ServerHealthy() bool {
api := constant.SERVICE_BASE_PATH + "/operator/metrics"
result, err := proxy.nacosServer.ReqApi(api, map[string]string{}, http.MethodGet)
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
return false
}
if result != "" {
status, err := jsonparser.GetString([]byte(result), "status")
if err != nil {
logger.Errorf("namespaceId:[%s] sending server healthy failed!,result:%s error:%+v", proxy.clientConfig.NamespaceId, result, err)
} else {
return status == "UP"
}
}
return false
}
func (proxy *NamingProxy) QueryList(serviceName string, clusters string, udpPort int, healthyOnly bool) (string, error) {
param := make(map[string]string)
param["namespaceId"] = proxy.clientConfig.NamespaceId
param["serviceName"] = serviceName
param["app"] = proxy.clientConfig.AppName
param["clusters"] = clusters
param["udpPort"] = strconv.Itoa(udpPort)
param["healthyOnly"] = strconv.FormatBool(healthyOnly)
param["clientIP"] = util.LocalIP()
api := constant.SERVICE_PATH + "/list"
return proxy.nacosServer.ReqApi(api, param, http.MethodGet)
}
func (proxy *NamingProxy) GetAllServiceInfoList(namespace, groupName string, pageNo, pageSize uint32) (string, error) {
param := make(map[string]string)
param["namespaceId"] = namespace
param["groupName"] = groupName
param["pageNo"] = strconv.Itoa(int(pageNo))
param["pageSize"] = strconv.Itoa(int(pageSize))
api := constant.SERVICE_INFO_PATH + "/list"
return proxy.nacosServer.ReqApi(api, param, http.MethodGet)
}

View File

@ -0,0 +1,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 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)
DeregisterInstance(serviceName string, groupName string, instance model.Instance) (bool, error)
GetServiceList(pageNo uint32, pageSize uint32, groupName 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,150 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: 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
}
// 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 string, selector *model.ExpressionSelector) (model.ServiceList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetServiceList", pageNo, pageSize, groupName, 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, 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, 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,114 @@
/*
* 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/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(clientCfg constant.ClientConfig, serverCfgs []constant.ServerConfig,
httpAgent http_agent.IHttpAgent, serviceInfoHolder *naming_cache.ServiceInfoHolder) (naming_proxy.INamingProxy, error) {
nacosServer, err := nacos_server.NewNacosServer(serverCfgs, clientCfg, httpAgent, clientCfg.TimeoutMs, clientCfg.Endpoint)
if err != nil {
return nil, err
}
httpClientProxy, err := naming_http.NewNamingHttpProxy(clientCfg, nacosServer, serviceInfoHolder)
if err != nil {
return nil, err
}
grpcClientProxy, err := naming_grpc.NewNamingGrpcProxy(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) 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 string, selector *model.ExpressionSelector) (model.ServiceList, error) {
return proxy.grpcClientProxy.GetServiceList(pageNo, pageSize, groupName, 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) {
serviceNameWithGroup := util.GetServiceCacheKey(util.GetGroupName(serviceName, groupName), clusters)
serviceInfo, ok := proxy.serviceInfoHolder.ServiceInfoMap.Get(serviceNameWithGroup)
if !ok {
result, err := proxy.grpcClientProxy.Subscribe(serviceName, groupName, clusters)
if err != nil {
return model.Service{}, err
}
serviceInfo = result
}
service := serviceInfo.(model.Service)
proxy.serviceInfoHolder.ProcessService(&service)
return service, nil
}
func (proxy *NamingProxyDelegate) Unsubscribe(serviceName, groupName, clusters string) error {
proxy.serviceInfoHolder.StopUpdateIfContain(util.GetGroupName(serviceName, groupName), clusters)
return proxy.grpcClientProxy.Unsubscribe(serviceName, groupName, clusters)
}
func (proxy *NamingProxyDelegate) CloseClient() {
proxy.grpcClientProxy.CloseClient()
}

View File

@ -0,0 +1,75 @@
/*
* 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 (
"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 {
serviceInfoHolder *naming_cache.ServiceInfoHolder
updateThreadNum int
namingProxy naming_proxy.INamingProxy
}
func NewServiceInfoUpdater(serviceInfoHolder *naming_cache.ServiceInfoHolder, updateThreadNum int,
namingProxy naming_proxy.INamingProxy) *ServiceInfoUpdater {
return &ServiceInfoUpdater{
serviceInfoHolder: serviceInfoHolder,
updateThreadNum: updateThreadNum,
namingProxy: namingProxy,
}
}
func (s *ServiceInfoUpdater) asyncUpdateService() {
sema := util.NewSemaphore(s.updateThreadNum)
for {
for _, v := range s.serviceInfoHolder.ServiceInfoMap.Items() {
service := v.(model.Service)
lastRefTime, ok := s.serviceInfoHolder.UpdateTimeMap.Get(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() {
s.updateServiceNow(service.Name, service.GroupName, service.Clusters)
sema.Release()
}()
}
}
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("QueryList return error!serviceName:%s cluster:%s err:%+v", serviceName, clusters, err)
return
}
s.serviceInfoHolder.ProcessService(result)
}

View File

@ -1,97 +0,0 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package naming_client
import (
"errors"
"github.com/nacos-group/nacos-sdk-go/clients/cache"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
)
type SubscribeCallback struct {
callbackFuncsMap cache.ConcurrentMap
}
func NewSubscribeCallback() SubscribeCallback {
ed := SubscribeCallback{}
ed.callbackFuncsMap = cache.NewConcurrentMap()
return ed
}
func (ed *SubscribeCallback) AddCallbackFuncs(serviceName string, clusters string, callbackFunc *func(services []model.SubscribeService, err error)) {
logger.Info("adding " + serviceName + " with " + clusters + " to listener map")
key := util.GetServiceCacheKey(serviceName, clusters)
var funcs []*func(services []model.SubscribeService, err error)
old, ok := ed.callbackFuncsMap.Get(key)
if ok {
funcs = append(funcs, old.([]*func(services []model.SubscribeService, err error))...)
}
funcs = append(funcs, callbackFunc)
ed.callbackFuncsMap.Set(key, funcs)
}
func (ed *SubscribeCallback) RemoveCallbackFuncs(serviceName string, clusters string, callbackFunc *func(services []model.SubscribeService, err error)) {
logger.Info("removing " + serviceName + " with " + clusters + " to listener map")
key := util.GetServiceCacheKey(serviceName, clusters)
funcs, ok := ed.callbackFuncsMap.Get(key)
if ok && funcs != nil {
var newFuncs []*func(services []model.SubscribeService, err error)
for _, funcItem := range funcs.([]*func(services []model.SubscribeService, err error)) {
if funcItem != callbackFunc {
newFuncs = append(newFuncs, funcItem)
}
}
ed.callbackFuncsMap.Set(key, newFuncs)
}
}
func (ed *SubscribeCallback) ServiceChanged(service *model.Service) {
if service == nil || service.Name == "" {
return
}
key := util.GetServiceCacheKey(service.Name, service.Clusters)
funcs, ok := ed.callbackFuncsMap.Get(key)
if ok {
for _, funcItem := range funcs.([]*func(services []model.SubscribeService, err error)) {
var subscribeServices []model.SubscribeService
if len(service.Hosts) == 0 {
(*funcItem)(subscribeServices, errors.New("[client.Subscribe] subscribe failed,hosts is empty"))
return
}
for _, host := range service.Hosts {
subscribeService := model.SubscribeService{
Valid: host.Valid,
Port: host.Port,
Ip: host.Ip,
Metadata: host.Metadata,
ServiceName: host.ServiceName,
ClusterName: host.ClusterName,
Weight: host.Weight,
InstanceId: host.InstanceId,
Enable: host.Enable,
Healthy: host.Healthy,
}
subscribeServices = append(subscribeServices, subscribeService)
}
(*funcItem)(subscribeServices, nil)
}
}
}

View File

@ -20,9 +20,7 @@ import (
"os"
"time"
"github.com/nacos-group/nacos-sdk-go/common/file"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"gopkg.in/natefinch/lumberjack.v2"
"github.com/nacos-group/nacos-sdk-go/v2/common/file"
)
func NewClientConfig(opts ...ClientOption) *ClientConfig {
@ -48,13 +46,6 @@ func NewClientConfig(opts ...ClientOption) *ClientConfig {
// ClientOption ...
type ClientOption func(*ClientConfig)
// WithCustomLogger ...
func WithCustomLogger(logger logger.Logger) ClientOption {
return func(config *ClientConfig) {
config.CustomLogger = logger
}
}
// WithTimeoutMs ...
func WithTimeoutMs(timeoutMs uint64) ClientOption {
return func(config *ClientConfig) {
@ -62,6 +53,13 @@ func WithTimeoutMs(timeoutMs uint64) ClientOption {
}
}
// WithAppName ...
func WithAppName(appName string) ClientOption {
return func(config *ClientConfig) {
config.AppName = appName
}
}
// WithBeatInterval ...
func WithBeatInterval(beatInterval int64) ClientOption {
return func(config *ClientConfig) {
@ -170,20 +168,19 @@ func WithLogLevel(logLevel string) ClientOption {
// WithLogSampling ...
func WithLogSampling(tick time.Duration, initial int, thereafter int) ClientOption {
return func(config *ClientConfig) {
config.LogSampling = &logger.SamplingConfig{Initial: initial, Thereafter: thereafter, Tick: tick}
config.LogSampling = &ClientLogSamplingConfig{initial, thereafter, tick}
}
}
// WithLogRollingConfig ...
func WithLogRollingConfig(rollingConfig *lumberjack.Logger) ClientOption {
func WithLogRollingConfig(rollingConfig *ClientLogRollingConfig) ClientOption {
return func(config *ClientConfig) {
config.LogRollingConfig = rollingConfig
}
}
// WithLogStdout ...
func WithLogStdout(logStdout bool) ClientOption {
func WithTLS(tlsCfg TLSConfig) ClientOption {
return func(config *ClientConfig) {
config.LogStdout = logStdout
config.TLSCfg = tlsCfg
}
}

View File

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

View File

@ -16,41 +16,80 @@
package constant
import (
"github.com/nacos-group/nacos-sdk-go/common/logger"
"gopkg.in/natefinch/lumberjack.v2"
)
import "time"
type ServerConfig struct {
Scheme string //the nacos server scheme
ContextPath string //the nacos server contextpath
IpAddr string //the nacos server address
Port uint64 //the nacos server port
Scheme string // the nacos server scheme,default=http,this is not required in 2.0
ContextPath string // the nacos server contextpath,default=/nacos,this is not required in 2.0
IpAddr string // the nacos server address
Port uint64 // nacos server port
GrpcPort uint64 // nacos server grpc port, default=server port + 1000, this is not required
}
type ClientConfig struct {
TimeoutMs uint64 // timeout for requesting Nacos server, default value is 10000ms
ListenInterval uint64 // Deprecated
BeatInterval int64 // the time interval for sending beat to server,default value is 5000ms
NamespaceId string // the namespaceId of Nacos.When namespace is public, fill in the blank string here.
AppName string // the appName
Endpoint string // the endpoint for get Nacos server addresses
RegionId string // the regionId for kms
AccessKey string // the AccessKey for kms
SecretKey string // the SecretKey for kms
OpenKMS bool // it's to open kms,default is false. https://help.aliyun.com/product/28933.html
CacheDir string // the directory for persist nacos service info,default value is current path
UpdateThreadNum int // the number of gorutine for update nacos service info,default value is 20
NotLoadCacheAtStart bool // not to load persistent nacos service info in CacheDir at start time
UpdateCacheWhenEmpty bool // update cache when get empty service instance from server
Username string // the username for nacos auth
Password string // the password for nacos auth
LogDir string // the directory for log, default is current path
LogLevel string // the level of log, it's must be debug,info,warn,error, default value is info
LogSampling *logger.SamplingConfig // the sampling config of log
ContextPath string // the nacos server contextpath
LogRollingConfig *lumberjack.Logger // the log rolling config
CustomLogger logger.Logger // the custom log interface ,With a custom Logger (nacos sdk will not provide log cutting and archiving capabilities)
LogStdout bool // the stdout redirect for log, default is false
TimeoutMs uint64 // timeout for requesting Nacos server, default value is 10000ms
ListenInterval uint64 // Deprecated
BeatInterval int64 // the time interval for sending beat to server,default value is 5000ms
NamespaceId string // the namespaceId of Nacos.When namespace is public, fill in the blank string here.
AppName string // the appName
AppKey string // the client identity information
Endpoint string // the endpoint for get Nacos server addresses
RegionId string // the regionId for kms
AccessKey string // the AccessKey for kms
SecretKey string // the SecretKey for kms
OpenKMS bool // it's to open kms,default is false. https://help.aliyun.com/product/28933.html
CacheDir string // the directory for persist nacos service info,default value is current path
UpdateThreadNum int // the number of gorutine for update nacos service info,default value is 20
NotLoadCacheAtStart bool // not to load persistent nacos service info in CacheDir at start time
UpdateCacheWhenEmpty bool // update cache when get empty service instance from server
Username string // the username for nacos auth
Password string // the password for nacos auth
LogDir string // the directory for log, default is current path
LogLevel string // the level of log, it's must be debug,info,warn,error, default value is info
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
}
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 {
Enable bool // enable tls
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
}

View File

@ -16,6 +16,8 @@
package constant
import "time"
const (
KEY_USERNAME = "username"
KEY_PASSWORD = "password"
@ -66,7 +68,7 @@ const (
KEY_BEAT = "beat"
KEY_DOM = "dom"
DEFAULT_CONTEXT_PATH = "/nacos"
CLIENT_VERSION = "Nacos-Go-Client:v1.0.1"
CLIENT_VERSION = "Nacos-Go-Client:v2.0.0"
REQUEST_DOMAIN_RETRY_TIME = 3
SERVICE_INFO_SPLITER = "@@"
CONFIG_INFO_SPLITER = "@@"
@ -75,7 +77,24 @@ const (
NAMING_INSTANCE_ID_SPLITTER = "#"
DefaultClientErrorCode = "SDK.NacosError"
DEFAULT_SERVER_SCHEME = "http"
WINDOWS_LEGAL_NAME_SPLITER = "&&"
OS_WINDOWS = "windows"
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"
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"
FAILOVER_FILE_SUFFIX = "_failover"
)

View File

@ -61,3 +61,10 @@ func WithPort(port uint64) ServerOption {
config.Port = port
}
}
//WithGrpcPort set grpc port for server
func WithGrpcPort(port uint64) ServerOption {
return func(config *ServerConfig) {
config.GrpcPort = port
}
}

View File

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

View File

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

View File

@ -21,6 +21,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
)
var osType string
@ -38,16 +39,51 @@ func init() {
}
func MkdirIfNecessary(createDir string) (err error) {
return os.MkdirAll(createDir, os.ModePerm)
s := strings.Split(createDir, path)
startIndex := 0
dir := ""
if s[0] == "" {
startIndex = 1
} else {
dir, _ = os.Getwd() //当前的目录
}
for i := startIndex; i < len(s); i++ {
var d string
if osType == WINDOWS && filepath.IsAbs(createDir) {
d = strings.Join(s[startIndex:i+1], path)
} else {
d = dir + path + strings.Join(s[startIndex:i+1], path)
}
if _, e := os.Stat(d); os.IsNotExist(e) {
err = os.Mkdir(d, os.ModePerm) //在当前目录下生成md目录
if err != nil {
break
}
}
}
return err
}
func GetCurrentPath() string {
dir, err := os.Getwd() //当前的目录
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
dir, err = filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Println("can not get current path")
}
log.Println("can not get current path")
}
return dir
}
func IsExistFile(filePath string) bool {
if len(filePath) == 0 {
return false
}
_, err := os.Stat(filePath)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
return false
}

View File

@ -22,7 +22,7 @@ import (
"time"
)
func delete(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
func delete(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.HasSuffix(path, "?") {
path = path + "?"
}
@ -32,7 +32,6 @@ func delete(path string, header http.Header, timeoutMs uint64, params map[string
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

@ -22,7 +22,7 @@ import (
"time"
)
func get(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
func get(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
if !strings.HasSuffix(path, "?") {
path = path + "?"
}
@ -34,7 +34,6 @@ func get(path string, header http.Header, timeoutMs uint64, params map[string]st
path = path[:len(path)-1]
}
client := http.Client{}
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
request, errNew := http.NewRequest(http.MethodGet, path, nil)
if errNew != nil {

View File

@ -20,17 +20,25 @@ import (
"io/ioutil"
"net/http"
"github.com/go-errors/errors"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/tls"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/pkg/errors"
)
type HttpAgent struct {
TlsConfig constant.TLSConfig
}
func (agent *HttpAgent) Get(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return get(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return get(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) RequestOnlyResult(method string, path string, header http.Header, timeoutMs uint64, params map[string]string) string {
@ -56,7 +64,7 @@ func (agent *HttpAgent) RequestOnlyResult(method string, path string, header htt
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],err:%+v", method, path, util.ToJsonString(header), util.ToJsonString(params), err)
return ""
}
if response.StatusCode != 200 {
if response.StatusCode != constant.RESPONSE_CODE_SUCCESS {
logger.Errorf("request method[%s],request path[%s],header:[%s],params:[%s],status code error:%d", method, path, util.ToJsonString(header), util.ToJsonString(params), response.StatusCode)
return ""
}
@ -92,13 +100,37 @@ func (agent *HttpAgent) Request(method string, path string, header http.Header,
}
func (agent *HttpAgent) Post(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return post(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return post(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) Delete(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return delete(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return delete(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) Put(path string, header http.Header, timeoutMs uint64,
params map[string]string) (response *http.Response, err error) {
return put(path, header, timeoutMs, params)
client, err := agent.createClient()
if err != nil {
return nil, err
}
return put(client, path, header, timeoutMs, params)
}
func (agent *HttpAgent) createClient() (*http.Client, error) {
if !agent.TlsConfig.Enable {
return &http.Client{}, nil
}
cfg, err := tls.NewTLS(agent.TlsConfig)
if err != nil {
return nil, err
}
return &http.Client{Transport: &http.Transport{TLSClientConfig: cfg}}, nil
}

View File

@ -20,11 +20,10 @@ import (
"strings"
"time"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
func post(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client := http.Client{}
func post(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
body := util.GetUrlFormedMap(params)

View File

@ -22,8 +22,7 @@ import (
"time"
)
func put(path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client := http.Client{}
func put(client *http.Client, path string, header http.Header, timeoutMs uint64, params map[string]string) (response *http.Response, err error) {
client.Timeout = time.Millisecond * time.Duration(timeoutMs)
var body string
for key, value := range params {

View File

@ -21,6 +21,7 @@ import (
"sync"
"time"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
@ -40,12 +41,9 @@ var levelMap = map[string]zapcore.Level{
type Config struct {
Level string
LogFileName string
Sampling *SamplingConfig
AppendToStdout bool
LogRollingConfig *lumberjack.Logger
LogDir string
CustomLogger Logger
LogStdout bool
}
type SamplingConfig struct {
@ -86,35 +84,53 @@ func init() {
EncodeCaller: zapcore.ShortCallerEncoder,
}
zapLoggerConfig.EncoderConfig = zapLoggerEncoderConfig
defaultLogger, _ := zapLoggerConfig.Build(zap.AddCaller(), zap.AddCallerSkip(1))
setLogger(&NacosLogger{defaultLogger.Sugar()})
zapLogger, _ := zapLoggerConfig.Build(zap.AddCaller(), zap.AddCallerSkip(1))
SetLogger(&NacosLogger{zapLogger.Sugar()})
}
func BuildLoggerConfig(clientConfig constant.ClientConfig) Config {
loggerConfig := Config{
Level: clientConfig.LogLevel,
AppendToStdout: clientConfig.AppendToStdout,
}
if clientConfig.LogSampling != nil {
loggerConfig.Sampling = &SamplingConfig{
Initial: clientConfig.LogSampling.Initial,
Thereafter: clientConfig.LogSampling.Thereafter,
Tick: clientConfig.LogSampling.Tick,
}
}
loggerConfig.LogRollingConfig = &lumberjack.Logger{
Filename: clientConfig.LogDir + string(os.PathSeparator) + constant.LOG_FILE_NAME,
}
logRollingConfig := clientConfig.LogRollingConfig
if logRollingConfig != nil {
loggerConfig.LogRollingConfig.MaxSize = logRollingConfig.MaxSize
loggerConfig.LogRollingConfig.MaxAge = logRollingConfig.MaxAge
loggerConfig.LogRollingConfig.MaxBackups = logRollingConfig.MaxBackups
loggerConfig.LogRollingConfig.LocalTime = logRollingConfig.LocalTime
loggerConfig.LogRollingConfig.Compress = logRollingConfig.Compress
}
return loggerConfig
}
// InitLogger is init global logger for nacos
func InitLogger(config Config) (err error) {
l, err := initNacosLogger(config)
if err != nil {
return err
}
setLogger(l)
logLock.Lock()
defer logLock.Unlock()
logger, err = InitNacosLogger(config)
return
}
// InitNacosLogger is init nacos default logger
func initNacosLogger(config Config) (Logger, error) {
if config.CustomLogger != nil {
return &NacosLogger{config.CustomLogger}, nil
}
func InitNacosLogger(config Config) (Logger, error) {
logLevel := getLogLevel(config.Level)
encoder := getEncoder()
writer := config.getLogWriter()
core := zapcore.NewCore(zapcore.NewConsoleEncoder(encoder), writer, logLevel)
if config.Sampling != nil {
core = zapcore.NewSamplerWithOptions(core, config.Sampling.Tick, config.Sampling.Initial, config.Sampling.Thereafter)
if config.AppendToStdout {
writer = zapcore.NewMultiWriteSyncer(writer, zapcore.AddSync(os.Stdout))
}
core := zapcore.NewCore(zapcore.NewConsoleEncoder(encoder), writer, logLevel)
zaplogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
return &NacosLogger{zaplogger.Sugar()}, nil
}
@ -134,7 +150,7 @@ func getEncoder() zapcore.EncoderConfig {
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
@ -142,7 +158,7 @@ func getEncoder() zapcore.EncoderConfig {
}
//SetLogger sets logger for sdk
func setLogger(log Logger) {
func SetLogger(log Logger) {
logLock.Lock()
defer logLock.Unlock()
logger = log
@ -156,12 +172,5 @@ func GetLogger() Logger {
// getLogWriter get Lumberjack writer by LumberjackConfig
func (c *Config) getLogWriter() zapcore.WriteSyncer {
if c.LogRollingConfig == nil {
c.LogRollingConfig = &lumberjack.Logger{}
}
c.LogRollingConfig.Filename = c.LogDir + string(os.PathSeparator) + c.LogFileName
if c.LogStdout {
return zapcore.NewMultiWriteSyncer(zapcore.AddSync(c.LogRollingConfig), zapcore.AddSync(os.Stdout))
}
return zapcore.AddSync(c.LogRollingConfig)
}

View File

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

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

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

View File

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

View File

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

View File

@ -20,7 +20,6 @@ import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"math/rand"
@ -29,50 +28,65 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/common/security"
"github.com/nacos-group/nacos-sdk-go/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/pkg/errors"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/monitor"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/nacos_error"
"github.com/nacos-group/nacos-sdk-go/v2/common/security"
"github.com/nacos-group/nacos-sdk-go/v2/inner/uuid"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
type NacosServer struct {
sync.RWMutex
securityLogin security.AuthClient
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
endpoint string
lastSrvRefTime int64
vipSrvRefInterMills int64
contextPath string
securityLogin security.AuthClient
serverList []constant.ServerConfig
httpAgent http_agent.IHttpAgent
timeoutMs uint64
endpoint string
lastSrvRefTime int64
vipSrvRefInterMills int64
contextPath string
currentIndex int32
ServerSrcChangeSignal chan struct{}
}
func NewNacosServer(serverList []constant.ServerConfig, clientCfg constant.ClientConfig, httpAgent http_agent.IHttpAgent, timeoutMs uint64, endpoint string) (*NacosServer, error) {
if len(serverList) == 0 && endpoint == "" {
severLen := len(serverList)
if severLen == 0 && endpoint == "" {
return &NacosServer{}, errors.New("both serverlist and endpoint are empty")
}
securityLogin := security.NewAuthClient(clientCfg, serverList, httpAgent)
ns := NacosServer{
serverList: serverList,
securityLogin: securityLogin,
httpAgent: httpAgent,
timeoutMs: timeoutMs,
endpoint: endpoint,
vipSrvRefInterMills: 10000,
contextPath: clientCfg.ContextPath,
serverList: serverList,
securityLogin: securityLogin,
httpAgent: httpAgent,
timeoutMs: timeoutMs,
endpoint: endpoint,
vipSrvRefInterMills: 10000,
contextPath: clientCfg.ContextPath,
ServerSrcChangeSignal: make(chan struct{}, 1),
}
if severLen > 0 {
ns.currentIndex = rand.Int31n(int32(severLen))
}
ns.initRefreshSrvIfNeed()
_, err := securityLogin.Login()
if err != nil {
logger.Errorf("login has error %+v", err)
return &ns, err
}
securityLogin.AutoRefresh()
@ -81,11 +95,12 @@ func NewNacosServer(serverList []constant.ServerConfig, clientCfg constant.Clien
func (server *NacosServer) callConfigServer(api string, params map[string]string, newHeaders map[string]string,
method string, curServer string, contextPath string, timeoutMS uint64) (result string, err error) {
start := time.Now()
if contextPath == "" {
contextPath = constant.WEB_CONTEXT
}
signHeaders := getSignHeaders(params, newHeaders)
signHeaders := GetSignHeaders(params, newHeaders["secretKey"])
url := curServer + contextPath + api
@ -105,15 +120,15 @@ func (server *NacosServer) callConfigServer(api string, params map[string]string
return
}
headers["RequestId"] = []string{uid.String()}
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
headers["Spas-AccessKey"] = []string{newHeaders["accessKey"]}
headers["Timestamp"] = []string{signHeaders["timeStamp"]}
headers["Timestamp"] = []string{signHeaders["Timestamp"]}
headers["Spas-Signature"] = []string{signHeaders["Spas-Signature"]}
injectSecurityInfo(server, params)
server.InjectSecurityInfo(params)
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, timeoutMS, params)
monitor.GetConfigRequestMonitor(method, url, util.GetStatusCode(response)).Observe(float64(time.Now().Nanosecond() - start.Nanosecond()))
if err != nil {
return
}
@ -124,7 +139,7 @@ func (server *NacosServer) callConfigServer(api string, params map[string]string
return
}
result = string(bytes)
if response.StatusCode == 200 {
if response.StatusCode == constant.RESPONSE_CODE_SUCCESS {
return
} else {
err = nacos_error.NewNacosError(strconv.Itoa(response.StatusCode), string(bytes), nil)
@ -133,6 +148,7 @@ func (server *NacosServer) callConfigServer(api string, params map[string]string
}
func (server *NacosServer) callServer(api string, params map[string]string, method string, curServer string, contextPath string) (result string, err error) {
start := time.Now()
if contextPath == "" {
contextPath = constant.WEB_CONTEXT
}
@ -152,7 +168,7 @@ func (server *NacosServer) callServer(api string, params map[string]string, meth
headers["Request-Module"] = []string{"Naming"}
headers["Content-Type"] = []string{"application/x-www-form-urlencoded;charset=utf-8"}
injectSecurityInfo(server, params)
server.InjectSecurityInfo(params)
var response *http.Response
response, err = server.httpAgent.Request(method, url, headers, server.timeoutMs, params)
@ -166,7 +182,8 @@ func (server *NacosServer) callServer(api string, params map[string]string, meth
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))
@ -180,7 +197,7 @@ func (server *NacosServer) ReqConfigApi(api string, params map[string]string, he
return "", errors.New("server list is empty")
}
injectSecurityInfo(server, params)
server.InjectSecurityInfo(params)
//only one server,retry request when error
var err error
@ -193,7 +210,6 @@ func (server *NacosServer) ReqConfigApi(api string, params map[string]string, he
}
logger.Errorf("api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s>", api, method, util.ToJsonString(params), err, result)
}
return "", err
} else {
index := rand.Intn(len(srvs))
for i := 1; i <= len(srvs); i++ {
@ -205,8 +221,8 @@ func (server *NacosServer) ReqConfigApi(api string, params map[string]string, he
logger.Errorf("[ERROR] api<%s>,method:<%s>, params:<%s>, call domain error:<%+v> , result:<%s> \n", api, method, util.ToJsonString(params), err, result)
index = (index + i) % len(srvs)
}
return "", err
}
return "", errors.Wrapf(err, "retry %d times request failed!", constant.REQUEST_DOMAIN_RETRY_TIME)
}
func (server *NacosServer) ReqApi(api string, params map[string]string, method string) (string, error) {
@ -214,13 +230,12 @@ func (server *NacosServer) ReqApi(api string, params map[string]string, method s
if srvs == nil || len(srvs) == 0 {
return "", errors.New("server list is empty")
}
var (
result string
err error
)
injectSecurityInfo(server, params)
server.InjectSecurityInfo(params)
//only one server,retry request when error
var err error
var result string
if len(srvs) == 1 {
for i := 0; i < constant.REQUEST_DOMAIN_RETRY_TIME; i++ {
result, err = server.callServer(api, params, method, getAddress(srvs[0]), srvs[0].ContextPath)
@ -241,7 +256,7 @@ func (server *NacosServer) ReqApi(api string, params map[string]string, method s
index = (index + i) % len(srvs)
}
}
return "", fmt.Errorf("retry%stimes request failed,err=%v", strconv.Itoa(constant.REQUEST_DOMAIN_RETRY_TIME), err)
return "", errors.Wrapf(err, "retry %d times request failed!", constant.REQUEST_DOMAIN_RETRY_TIME)
}
func (server *NacosServer) initRefreshSrvIfNeed() {
@ -259,7 +274,7 @@ func (server *NacosServer) initRefreshSrvIfNeed() {
}
func (server *NacosServer) refreshServerSrvIfNeed() {
if len(server.serverList) > 0 || util.CurrentMillis()-server.lastSrvRefTime < server.vipSrvRefInterMills {
if util.CurrentMillis()-server.lastSrvRefTime < server.vipSrvRefInterMills && len(server.serverList) > 0 {
return
}
@ -295,6 +310,7 @@ func (server *NacosServer) refreshServerSrvIfNeed() {
server.Lock()
logger.Infof("server list is updated, old: <%v>,new:<%v>", server.serverList, servers)
server.serverList = servers
server.ServerSrcChangeSignal <- struct{}{}
server.lastSrvRefTime = util.CurrentMillis()
server.Unlock()
}
@ -307,13 +323,27 @@ func (server *NacosServer) GetServerList() []constant.ServerConfig {
return server.serverList
}
func injectSecurityInfo(server *NacosServer, param map[string]string) {
func (server *NacosServer) InjectSecurityInfo(param map[string]string) {
accessToken := server.securityLogin.GetAccessToken()
if accessToken != "" {
param[constant.KEY_ACCESS_TOKEN] = accessToken
}
}
func (server *NacosServer) InjectSign(request rpc_request.IRequest, param map[string]string, clientConfig constant.ClientConfig) {
if clientConfig.AccessKey == "" || clientConfig.SecretKey == "" {
return
}
sts := request.GetStringToSign()
if sts == "" {
return
}
signature := signWithhmacSHA1Encrypt(sts, clientConfig.SecretKey)
param["data"] = sts
param["signature"] = signature
param["ak"] = clientConfig.AccessKey
}
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))
@ -321,7 +351,34 @@ func getAddress(cfg constant.ServerConfig) string {
return cfg.Scheme + "://" + cfg.IpAddr + ":" + strconv.Itoa(int(cfg.Port))
}
func getSignHeaders(params map[string]string, newHeaders map[string]string) map[string]string {
func GetSignHeadersFromRequest(cr rpc_request.IConfigRequest, secretKey string) map[string]string {
resource := ""
if len(cr.GetGroup()) != 0 {
resource = cr.GetTenant() + "+" + cr.GetGroup()
} else {
resource = cr.GetGroup()
}
headers := map[string]string{}
timeStamp := strconv.FormatInt(util.CurrentMillis(), 10)
headers["Timestamp"] = timeStamp
signature := ""
if resource == "" {
signature = signWithhmacSHA1Encrypt(timeStamp, secretKey)
} else {
signature = signWithhmacSHA1Encrypt(resource+"+"+timeStamp, secretKey)
}
headers["Spas-Signature"] = signature
return headers
}
func GetSignHeaders(params map[string]string, secretKey string) map[string]string {
resource := ""
if len(params["tenant"]) != 0 {
@ -332,15 +389,15 @@ func getSignHeaders(params map[string]string, newHeaders map[string]string) map[
headers := map[string]string{}
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
headers["timeStamp"] = timeStamp
timeStamp := strconv.FormatInt(util.CurrentMillis(), 10)
headers["Timestamp"] = timeStamp
signature := ""
if resource == "" {
signature = signWithhmacSHA1Encrypt(timeStamp, newHeaders["secretKey"])
signature = signWithhmacSHA1Encrypt(timeStamp, secretKey)
} else {
signature = signWithhmacSHA1Encrypt(resource+"+"+timeStamp, newHeaders["secretKey"])
signature = signWithhmacSHA1Encrypt(resource+"+"+timeStamp, secretKey)
}
headers["Spas-Signature"] = signature
@ -356,3 +413,18 @@ func signWithhmacSHA1Encrypt(encryptText, encryptKey string) string {
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
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
}
func (server *NacosServer) InjectSkAk(params map[string]string, clientConfig constant.ClientConfig) {
if clientConfig.AccessKey != "" {
params["Spas-AccessKey"] = clientConfig.AccessKey
}
}

View File

@ -19,7 +19,7 @@ package nacos_server
import (
"testing"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/stretchr/testify/assert"
)

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,262 @@
/*
* 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"
"encoding/json"
"io"
"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
}
func NewGrpcClient(clientName string, nacosServer *nacos_server.NacosServer) *GrpcClient {
rpcClient := &GrpcClient{
&RpcClient{
Name: clientName,
labels: make(map[string]string, 8),
rpcClientStatus: INITIALIZED,
eventChan: make(chan ConnectionEvent),
reconnectionChan: make(chan ReconnectContext, 1),
nacosServer: nacosServer,
serverRequestHandlerMapping: make(map[string]ServerRequestHandlerMapping, 8),
mux: new(sync.Mutex),
},
}
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 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()))
rpcPort := serverInfo.serverGrpcPort
if rpcPort == 0 {
rpcPort = serverInfo.serverPort + c.rpcPortOffset()
}
return grpc.Dial(serverInfo.serverIp+":"+strconv.FormatUint(rpcPort, 10), opts...)
}
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, err
}
client = nacos_grpc_service.NewRequestClient(conn)
response, err := serverCheck(client)
if err != nil {
conn.Close()
return nil, err
}
biStreamClient = nacos_grpc_service.NewBiRequestStreamClient(conn)
serverCheckResponse := response.(*rpc_response.ServerCheckResponse)
biStreamRequestClient, err := biStreamClient.RequestBiStream(context.Background())
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 1000
}
func (c *GrpcClient) bindBiRequestStream(streamClient nacos_grpc_service.BiRequestStream_RequestBiStreamClient, grpcConn *GrpcConnection) {
go func() {
for {
select {
case <-streamClient.Context().Done():
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("%s Request stream onCompleted, switch server", grpcConn.getConnectionId())
} else {
logger.Errorf("%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.Infof("%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
for i := 0; i <= 30; i++ {
payload, err := client.Request(context.Background(), 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()
mapping, ok := client.serverRequestHandlerMapping[payLoadType]
if !ok {
logger.Errorf("%s Unsupported payload type", grpcConn.getConnectionId())
return
}
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,105 @@
/*
* 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"
"encoding/json"
"fmt"
"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"
"github.com/nacos-group/nacos-sdk-go/v2/util"
"github.com/golang/protobuf/ptypes/any"
nacos_grpc_service "github.com/nacos-group/nacos-sdk-go/v2/api/grpc"
"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 {
return nil, err
}
responseFunc, ok := rpc_response.ClientResponseMapping[responsePayload.Metadata.GetType()]
if !ok {
return nil, errors.New(fmt.Sprintf("request:%s,unsupported response type:%s", request.GetRequestType(),
responsePayload.Metadata.GetType()))
}
response := responseFunc()
err = json.Unmarshal(responsePayload.GetBody().Value, response)
return response, err
}
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: &any.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: &any.Any{Value: []byte(r.GetBody())},
}
}

View File

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

View File

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

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

View File

@ -0,0 +1,99 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rpc_request
type ClientAbilities struct {
}
type InternalRequest struct {
*Request
Module string `json:"module"`
}
func NewInternalRequest() *InternalRequest {
request := Request{
Headers: make(map[string]string, 8),
}
return &InternalRequest{
Request: &request,
Module: "internal",
}
}
type HealthCheckRequest struct {
*InternalRequest
}
func NewHealthCheckRequest() *HealthCheckRequest {
return &HealthCheckRequest{
InternalRequest: NewInternalRequest(),
}
}
func (r *HealthCheckRequest) GetRequestType() string {
return "HealthCheckRequest"
}
type ConnectResetRequest struct {
*InternalRequest
ServerIp string
ServerPort string
}
func (r *ConnectResetRequest) GetRequestType() string {
return "ConnectResetRequest"
}
type ClientDetectionRequest struct {
*InternalRequest
}
func (r *ClientDetectionRequest) GetRequestType() string {
return "ClientDetectionRequest"
}
type ServerCheckRequest struct {
*InternalRequest
}
func NewServerCheckRequest() *ServerCheckRequest {
return &ServerCheckRequest{
InternalRequest: NewInternalRequest(),
}
}
func (r *ServerCheckRequest) GetRequestType() string {
return "ServerCheckRequest"
}
type ConnectionSetupRequest struct {
*InternalRequest
ClientVersion string `json:"clientVersion"`
Tenant string `json:"tenant"`
Labels map[string]string `json:"labels"`
ClientAbilities ClientAbilities `json:"clientAbilities"`
}
func NewConnectionSetupRequest() *ConnectionSetupRequest {
return &ConnectionSetupRequest{
InternalRequest: NewInternalRequest(),
}
}
func (r *ConnectionSetupRequest) GetRequestType() string {
return "ConnectionSetupRequest"
}

View File

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

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

View File

@ -0,0 +1,59 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rpc_response
import "github.com/nacos-group/nacos-sdk-go/v2/model"
type ConfigChangeBatchListenResponse struct {
*Response
ChangedConfigs []model.ConfigContext `json:"changedConfigs"`
}
func (c *ConfigChangeBatchListenResponse) GetResponseType() string {
return "ConfigChangeBatchListenResponse"
}
type ConfigQueryResponse struct {
*Response
Content string `json:"content"`
EncryptedDataKey string `json:"encryptedDataKey"`
ContentType string `json:"contentType"`
Md5 string `json:"md5"`
LastModified int64 `json:"lastModified"`
IsBeta bool `json:"isBeta"`
Tag bool `json:"tag"`
}
func (c *ConfigQueryResponse) GetResponseType() string {
return "ConfigQueryResponse"
}
type ConfigPublishResponse struct {
*Response
}
func (c *ConfigPublishResponse) GetResponseType() string {
return "ConfigPublishResponse"
}
type ConfigRemoveResponse struct {
*Response
}
func (c *ConfigRemoveResponse) GetResponseType() string {
return "ConfigRemoveResponse"
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rpc_response
import (
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
type ConnectResetResponse struct {
*Response
}
func (c *ConnectResetResponse) GetResponseType() string {
return "ConnectResetResponse"
}
type ClientDetectionResponse struct {
*Response
}
func (c *ClientDetectionResponse) GetResponseType() string {
return "ClientDetectionResponse"
}
type ServerCheckResponse struct {
*Response
ConnectionId string `json:"connectionId"`
}
func (c *ServerCheckResponse) GetResponseType() string {
return "ServerCheckResponse"
}
type InstanceResponse struct {
*Response
}
func (c *InstanceResponse) GetResponseType() string {
return "InstanceResponse"
}
type QueryServiceResponse struct {
*Response
ServiceInfo model.Service `json:"serviceInfo"`
}
func (c *QueryServiceResponse) GetResponseType() string {
return "QueryServiceResponse"
}
type SubscribeServiceResponse struct {
*Response
ServiceInfo model.Service `json:"serviceInfo"`
}
func (c *SubscribeServiceResponse) GetResponseType() string {
return "SubscribeServiceResponse"
}
type ServiceListResponse struct {
*Response
Count int `json:"count"`
ServiceNames []string `json:"serviceNames"`
}
func (c *ServiceListResponse) GetResponseType() string {
return "ServiceListResponse"
}
type NotifySubscriberResponse struct {
*Response
}
func (c *NotifySubscriberResponse) GetResponseType() string {
return "NotifySubscriberResponse"
}
type HealthCheckResponse struct {
*Response
}
func (c *HealthCheckResponse) GetResponseType() string {
return "HealthCheckResponse"
}
type ErrorResponse struct {
*Response
}
func (c *ErrorResponse) GetResponseType() string {
return "ErrorResponse"
}
type MockResponse struct {
*Response
}
func (c *MockResponse) GetResponseType() string {
return "MockResponse"
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rpc_response
import (
"strconv"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/util"
)
var ClientResponseMapping map[string]func() IResponse
func init() {
ClientResponseMapping = make(map[string]func() IResponse)
registerClientResponses()
}
type IResponse interface {
GetResponseType() string
SetRequestId(requestId string)
GetBody() string
GetErrorCode() int
IsSuccess() bool
GetResultCode() int
GetMessage() string
}
type Response struct {
ResultCode int `json:"resultCode"`
ErrorCode int `json:"errorCode"`
Success bool `json:"success"`
Message string `json:"message"`
RequestId string `json:"requestId"`
}
func (r *Response) SetRequestId(requestId string) {
r.RequestId = requestId
}
func (r *Response) GetBody() string {
return util.ToJsonString(r)
}
func (r *Response) IsSuccess() bool {
return r.Success
}
func (r *Response) GetErrorCode() int {
return r.ErrorCode
}
func (r *Response) GetResultCode() int {
return r.ResultCode
}
func (r *Response) GetMessage() string {
return r.Message
}
func registerClientResponse(response func() IResponse) {
responseType := response().GetResponseType()
if responseType == "" {
logger.Errorf("Register client response error: responseType is nil")
return
}
ClientResponseMapping[responseType] = response
}
func registerClientResponses() {
// register InstanceResponse.
registerClientResponse(func() IResponse {
return &InstanceResponse{Response: &Response{}}
})
// register QueryServiceResponse.
registerClientResponse(func() IResponse {
return &QueryServiceResponse{Response: &Response{}}
})
// register SubscribeServiceResponse.
registerClientResponse(func() IResponse {
return &SubscribeServiceResponse{Response: &Response{}}
})
// register ServiceListResponse.
registerClientResponse(func() IResponse {
return &ServiceListResponse{Response: &Response{}}
})
// register NotifySubscriberResponse.
registerClientResponse(func() IResponse {
return &NotifySubscriberResponse{Response: &Response{}}
})
// register HealthCheckResponse.
registerClientResponse(func() IResponse {
return &HealthCheckResponse{Response: &Response{}}
})
// register ErrorResponse.
registerClientResponse(func() IResponse {
return &ErrorResponse{Response: &Response{}}
})
//register ConfigChangeBatchListenResponse
registerClientResponse(func() IResponse {
return &ConfigChangeBatchListenResponse{Response: &Response{}}
})
//register ConfigQueryResponse
registerClientResponse(func() IResponse {
return &ConfigQueryResponse{Response: &Response{}}
})
//register ConfigPublishResponse
registerClientResponse(func() IResponse {
return &ConfigPublishResponse{Response: &Response{}}
})
//register ConfigRemoveResponse
registerClientResponse(func() IResponse {
return &ConfigRemoveResponse{Response: &Response{}}
})
}
// get grpc response status code with NA default.
func GetGrpcResponseStatusCode(response IResponse) string {
if response != nil {
return strconv.Itoa(response.GetResultCode())
} else {
return "NA"
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package rpc
import (
"strconv"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client/naming_cache"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_request"
"github.com/nacos-group/nacos-sdk-go/v2/common/remote/rpc/rpc_response"
)
//IServerRequestHandler to process the request from server side.
type IServerRequestHandler interface {
Name() string
//RequestReply Handle request from server.
RequestReply(request rpc_request.IRequest, rpcClient *RpcClient) rpc_response.IResponse
}
type ConnectResetRequestHandler struct {
}
func (c *ConnectResetRequestHandler) Name() string {
return "ConnectResetRequestHandler"
}
func (c *ConnectResetRequestHandler) RequestReply(request rpc_request.IRequest, rpcClient *RpcClient) rpc_response.IResponse {
connectResetRequest, ok := request.(*rpc_request.ConnectResetRequest)
if ok {
rpcClient.mux.Lock()
defer rpcClient.mux.Unlock()
if rpcClient.IsRunning() {
if connectResetRequest.ServerIp != "" {
serverPortNum, err := strconv.Atoi(connectResetRequest.ServerPort)
if err != nil {
logger.Errorf("ConnectResetRequest ServerPort type conversion error:%+v", err)
return nil
}
rpcClient.switchServerAsync(ServerInfo{serverIp: connectResetRequest.ServerIp, serverPort: uint64(serverPortNum)}, false)
} else {
rpcClient.switchServerAsync(ServerInfo{}, true)
}
}
return &rpc_response.ConnectResetResponse{Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS}}
}
return nil
}
type ClientDetectionRequestHandler struct {
}
func (c *ClientDetectionRequestHandler) Name() string {
return "ClientDetectionRequestHandler"
}
func (c *ClientDetectionRequestHandler) RequestReply(request rpc_request.IRequest, rpcClient *RpcClient) rpc_response.IResponse {
_, ok := request.(*rpc_request.ClientDetectionRequest)
if ok {
return &rpc_response.ClientDetectionResponse{
Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS},
}
}
return nil
}
type NamingPushRequestHandler struct {
ServiceInfoHolder *naming_cache.ServiceInfoHolder
}
func (*NamingPushRequestHandler) Name() string {
return "NamingPushRequestHandler"
}
func (c *NamingPushRequestHandler) RequestReply(request rpc_request.IRequest, rpcClient *RpcClient) rpc_response.IResponse {
notifySubscriberRequest, ok := request.(*rpc_request.NotifySubscriberRequest)
if ok {
c.ServiceInfoHolder.ProcessService(&notifySubscriberRequest.ServiceInfo)
return &rpc_response.NotifySubscriberResponse{
Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS},
}
}
return nil
}

View File

@ -18,7 +18,6 @@ package security
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strconv"
@ -26,9 +25,11 @@ import (
"sync/atomic"
"time"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/common/http_agent"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/pkg/errors"
"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"
)
type AuthClient struct {
@ -50,7 +51,6 @@ func NewAuthClient(clientCfg constant.ClientConfig, serverCfgs []constant.Server
serverCfgs: serverCfgs,
clientCfg: clientCfg,
agent: agent,
tokenTtl: 5, // default refresh token 5 second, if first login error
accessToken: &atomic.Value{},
}
@ -138,7 +138,7 @@ func (ac *AuthClient) login(server constant.ServerConfig) (bool, error) {
return false, err
}
if resp.StatusCode != 200 {
if resp.StatusCode != constant.RESPONSE_CODE_SUCCESS {
errMsg := string(bytes)
return false, errors.New(errMsg)
}

68
common/tls/tls.go Normal file
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 tls
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
// NewTLS returns a config structure is used to configure a TLS client
func NewTLS(c constant.TLSConfig) (tc *tls.Config, err error) {
tc = &tls.Config{}
if len(c.CertFile) > 0 && len(c.KeyFile) > 0 {
cert, err := certificate(c.CertFile, c.KeyFile)
if err != nil {
return nil, err
}
tc.Certificates = []tls.Certificate{*cert}
}
if len(c.CaFile) <= 0 {
tc.InsecureSkipVerify = true
return tc, nil
}
if len(c.ServerNameOverride) > 0 {
tc.ServerName = c.ServerNameOverride
}
tc.RootCAs, err = rootCert(c.CaFile)
return
}
func rootCert(caFile string) (*x509.CertPool, error) {
b, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, err
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
return nil, fmt.Errorf("credentials: failed to append certificates")
}
return cp, nil
}
func certificate(certFile, keyFile string) (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
return &cert, nil
}

137
common/tls/tls_test.go Normal file
View File

@ -0,0 +1,137 @@
/*
* 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 tls
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/stretchr/testify/assert"
)
var (
testCaCrt = []byte(`-----BEGIN CERTIFICATE-----
MIICojCCAYoCCQDbLXd9WTa7rTANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAh3
ZS1hcy1jYTAeFw0yMTA4MDQxNTE3MTlaFw00ODEyMjAxNTE3MTlaMBMxETAPBgNV
BAMMCHdlLWFzLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyc0D
ca+T6zwVnroauoVYQvPEx6R2jxmEgmCEclXegmO0rJ+23nP63nhgDvLN2Yv4olmv
d+eh1WfsnmqfdtqUcooZQIYZHWw5jWSYygZOwUWzfIclVFcyfkZnP7qTMGjYPn9Y
hOfdgSIh1c/DXrKFu1VQd9p3DevUD+ImAbxYJW4SMgYvliooPABbFU/sm3ZrHPwb
Ik8U1KlGHoYtw8KslD0INTfOOEYfQToeZtoAkoajykyteYYbI0kNVYBr2W3AOEXt
/QQkj/kAa1o8YKrVkufvi90UI/53SnJa0o5TDzXJCAu4jg4Xfpq0tVogFuEamMeI
f2R4JL77flG41nqN2QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCOYw0C4wJTbHly
LRmR7lJiLDkdObVKyQM6UUrbY62h9Fu02vYI9a8sj4xVSr3JKTXWBUwXSDqTUqgr
+9+sWoWxGwHbDINVHSsy2vnZlhGFkRkdWv3qgAPOn1Dc2ZMVzNEXRnmLFc1X2Ir/
niuk4cqSKwFE4IoJz9CHDDOlJzowimTwD6ReIrJDhi0pEFE6YtBVfRF5XPvz3AyG
mIFTX9LPRmCBnRi7We9cea+zuFarbjU6qDtf9jDfWANz1Gv6OHf0oM6BoCJ0jp0b
tJ5yJe4OCybgpb5bMZygBkGWozeQ5I/XzhkswNN0jVXeC3e0UWLscYvsgPVAM1kH
vZvo/wBG
-----END CERTIFICATE-----`)
testClientCrt = []byte(`-----BEGIN CERTIFICATE-----
MIICozCCAYsCCQDaqEi3maR5ojANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDDAh3
ZS1hcy1jYTAeFw0yMTA4MDQxNTE4MzFaFw0yMjA4MDQxNTE4MzFaMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPN
fcRRVPcinxUhS+Q1pvL6nZk7Gg7vcyoOqdX68R/iG/by/dL/wKbYzi2gyMyNN7Q8
o1+74inrsS5KMXAuyxrurdudWaoS9eWFzrd9r98+47kxUVwye5R4leT9+NCiI0mp
HOqEkOFv+X//kkCtCpDVj/XkfZJ+UrJHgAHvL9me/v5yUvLDDgu7/cdGU1tRwGQc
zabzk8SkvoQjMWaZ+R1eIWfeg9lYFyWQyYJhZkAqlBhlyDHw3FfrfPKjrxsI6uDq
ACeptHUuMZu6H6EzKDJtnx5DhSrwXTOwEcsOzTl60Hb2wT154CGi+7VbaZDGl6Uf
ZBkVAiSZvNCDJ0NGa9kCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAqjg4WeFbUcYC
Ko5R6UNTEYUvLYk45hks/Cmu5Mdqe3tFbPsr3EVdqd+zFJrINQQTlhZx14stJjIO
b3+eRX7Rldow7AI9Q2dyCQUoWiYmmco//4Mx1jObN2wMUd7tavhwg8RNdps1Yly2
/l0Vj2OhNDFnApqAiHZ0NGuCW7CLBvuD2XFCPZLCYFv0aQTw/Vr0+hHvNApmFYn0
4wiveiWUf98KKrp93lbzskAd3OZmoNx4bIo/J2Arcz0KzuliBgXDcGPOb4YLRLgs
QBhpY/VCGRat52Ys+sm6l/+2Cv+C2mHhn6V4BNVifaZIgOofXwzO4vaQO9sNTZ4f
qqJ4/s4nDg==
-----END CERTIFICATE-----`)
testClientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAs819xFFU9yKfFSFL5DWm8vqdmTsaDu9zKg6p1frxH+Ib9vL9
0v/AptjOLaDIzI03tDyjX7viKeuxLkoxcC7LGu6t251ZqhL15YXOt32v3z7juTFR
XDJ7lHiV5P340KIjSakc6oSQ4W/5f/+SQK0KkNWP9eR9kn5SskeAAe8v2Z7+/nJS
8sMOC7v9x0ZTW1HAZBzNpvOTxKS+hCMxZpn5HV4hZ96D2VgXJZDJgmFmQCqUGGXI
MfDcV+t88qOvGwjq4OoAJ6m0dS4xm7ofoTMoMm2fHkOFKvBdM7ARyw7NOXrQdvbB
PXngIaL7tVtpkMaXpR9kGRUCJJm80IMnQ0Zr2QIDAQABAoIBAFMiakpBSMXT7jY4
5Pwpin3CPuhAmXXaZSdHDGPx2VdiloeCJrZOpmb+y6XxN6bMjLr7Zpa3KoUzgwLi
LyWtnR9gyGZIxNKMXcG4MrJInO7eBzDziqjUdqtZbgUpIMhmj2ZZmRMeJFb4DSaP
prHc0IvTEvMgqKb5XYcs5BUA4OD/ihXvpW7GN4c/+iakJN0UBTI0/P/bTiYkERN8
ousw8UebrODyUbI20rqL/UO8UyOPIyifFpU29nvn/57Z42TzU+BqdxmJG5VJCR7w
lvJhA+jkldorHksHm1Z/9qDMj2UIvuPTJoa6L2t1utJRFgE+27QKHQ9Nv7IjbkUr
gdHO/QECgYEA5TneVmplJ4ARg8JIFYBotArcSj4f+Z2pZ36KO2CMklnnTFy2tAuw
766yxvZULCU5hr9AwlQwGkqt99o50a8WP4HBGjZ27r6CPODbvZvF7JnsFsD8z178
H52GNMO626KogDrC6aoJnYPJQAY+wGhR6Gg05goGoiKdUnhBOzKuEfECgYEAyM3O
TUch0FTBmKGA7IWGRL9bQBpw13UtZOokm5g5zTg+yDsQZ5BZCIgycZf77zQmIpbZ
TJe8xeFBI8fjfAF+UAfvzwwc4b3dSmD/jUSrv2gcKfCff2wZw8c8sYwbqNpSeX3l
Y2m7VJj5fw8I2vMOKjzISNKX55qNc8BGUuLiEGkCgYEA1Gb93ccyuhpSoGuLDdlx
q7sQiv7r9AmiqpK3lfON7iK+T6TtawIWTtHrOK+SKWHI31IiuK7377TZZPvibai2
jdw2yYpERE9lMPIOy7AnA2lROXhUCfdy2fzGGehwIgqj5kYMzCXSSRGPjvL6fKFt
nFPLCImrwdsfOgbSMv9wCpECgYA1n2fxEQbBmHCebrp77ug9IZCfnK/3iW4W3cPq
3QrKd7OkSsmFrnFoKt61oO2BIj7wy7G5l2esvAtmH7Hq4oc1nfj3JHft/ILEowR7
WBQ5J/claAFfyKFUu7bEfvK/85VEpk8Ebi69V6CAwqYNugxVUSf28m3oRkhx2a2t
4rKVyQKBgBYWALJLO3YwzpdelzVJiOPatVrphQarUKafsE32u/iBBvJwfpYpkclh
kJ4wLmJAMU8VAhvfSh6T8z8us9z3znONoUI0z6GzwbjROFRtd2WiffXvgcKfTacu
q9K53Jum9GDmkbUODa77sWR1zQsdrqSKywcjP/6FYXU9RMDqKUpm
-----END RSA PRIVATE KEY-----`)
)
func Test_NewTLS(t *testing.T) {
dir, err := ioutil.TempDir("", "tls-test")
if err != nil {
t.Errorf(err.Error())
}
defer os.RemoveAll(dir)
caPath, crtPath, keyPath := filepath.Join(dir, "ca.crt"), filepath.Join(dir, "client.crt"), filepath.Join(dir, "client.key")
ioutil.WriteFile(caPath, testCaCrt, 0666)
ioutil.WriteFile(crtPath, testClientCrt, 0666)
ioutil.WriteFile(keyPath, testClientKey, 0666)
t.Run("TestNoAuth", func(t *testing.T) {
cfg, err := NewTLS(constant.SkipVerifyConfig)
assert.Nil(t, err)
assert.Equal(t, true, cfg.InsecureSkipVerify)
assert.Nil(t, cfg.RootCAs)
assert.Nil(t, cfg.Certificates)
})
t.Run("TestClientAuth", func(t *testing.T) {
cfg, err := NewTLS(*constant.NewTLSConfig(
constant.WithCA(caPath, ""),
))
assert.Nil(t, err)
assert.Equal(t, false, cfg.InsecureSkipVerify)
assert.NotNil(t, cfg.RootCAs)
assert.Nil(t, cfg.Certificates)
})
t.Run("TestServerAuth", func(t *testing.T) {
cfg, err := NewTLS(*constant.NewTLSConfig(
constant.WithCA(caPath, ""),
constant.WithCertificate(crtPath, keyPath),
))
assert.Nil(t, err)
assert.Equal(t, false, cfg.InsecureSkipVerify)
assert.NotNil(t, cfg.RootCAs)
assert.NotNil(t, cfg.Certificates)
})
}

View File

@ -19,9 +19,9 @@ package main
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
func main() {

View File

@ -20,48 +20,28 @@ import (
"fmt"
"time"
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
func main() {
//create ServerConfig
sc := []constant.ServerConfig{
{
IpAddr: "console.nacos.io",
Port: 80,
},
}
//or a more graceful way to create ServerConfig
_ = []constant.ServerConfig{
*constant.NewServerConfig(
"console.nacos.io",
80,
constant.WithScheme("http"),
constant.WithContextPath("/nacos")),
*constant.NewServerConfig("127.0.0.1", 8848, constant.WithContextPath("/nacos")),
}
cc := constant.ClientConfig{
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", //namespace id
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
LogLevel: "debug",
LogStdout: true,
}
//or a more graceful way to create ClientConfig
_ = *constant.NewClientConfig(
constant.WithNamespaceId("e525eafa-f7d7-4029-83d9-008937f9d468"),
//create ClientConfig
cc := *constant.NewClientConfig(
constant.WithNamespaceId(""),
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
constant.WithCacheDir("/tmp/nacos/cache"),
constant.WithLogLevel("debug"),
constant.WithLogStdout(true),
)
// a more graceful way to create config client
// create config client
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
@ -88,7 +68,7 @@ func main() {
if err != nil {
fmt.Printf("PublishConfig err:%+v \n", err)
}
time.Sleep(1 * time.Second)
//get config
content, err := client.GetConfig(vo.ConfigParam{
DataId: "test-data",
@ -113,13 +93,15 @@ func main() {
},
})
time.Sleep(1 * time.Second)
_, err = client.PublishConfig(vo.ConfigParam{
DataId: "test-data",
Group: "test-group",
Content: "test-listen",
})
time.Sleep(2 * time.Second)
time.Sleep(1 * time.Second)
_, err = client.PublishConfig(vo.ConfigParam{
DataId: "test-data-2",
@ -135,14 +117,14 @@ func main() {
Group: "test-group",
})
time.Sleep(2 * time.Second)
time.Sleep(1 * time.Second)
_, err = client.DeleteConfig(vo.ConfigParam{
DataId: "test-data",
Group: "test-group",
})
time.Sleep(5 * time.Second)
time.Sleep(1 * time.Second)
searchPage, _ := client.SearchConfig(vo.SearchConfigParam{
searchPage, _ := client.SearchConfig(vo.SearchConfigParm{
Search: "blur",
DataId: "",
Group: "",

View File

@ -20,49 +20,30 @@ import (
"fmt"
"time"
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/model"
"github.com/nacos-group/nacos-sdk-go/util"
"github.com/nacos-group/nacos-sdk-go/vo"
"gopkg.in/natefinch/lumberjack.v2"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"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/util"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
func main() {
//create ServerConfig
sc := []constant.ServerConfig{
{
IpAddr: "console.nacos.io",
Port: 80,
},
}
//or a more graceful way to create ServerConfig
_ = []constant.ServerConfig{
*constant.NewServerConfig("console.nacos.io", 80),
*constant.NewServerConfig("127.0.0.1", 8848, constant.WithContextPath("/nacos")),
}
cc := constant.ClientConfig{
NamespaceId: "e525eafa-f7d7-4029-83d9-008937f9d468", //namespace id
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
LogRollingConfig: &lumberjack.Logger{MaxSize: 10},
LogLevel: "debug",
LogStdout: true,
}
//or a more graceful way to create ClientConfig
_ = *constant.NewClientConfig(
constant.WithNamespaceId("e525eafa-f7d7-4029-83d9-008937f9d468"),
//create ClientConfig
cc := *constant.NewClientConfig(
constant.WithNamespaceId(""),
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
constant.WithCacheDir("/tmp/nacos/cache"),
constant.WithLogLevel("debug"),
constant.WithLogRollingConfig(&lumberjack.Logger{MaxSize: 10}),
constant.WithLogStdout(true),
)
// a more graceful way to create naming client
// create naming client
client, err := clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &cc,
@ -74,12 +55,13 @@ func main() {
panic(err)
}
//Register with default cluster and group
//ClusterName=DEFAULT,GroupName=DEFAULT_GROUP
//Register
ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{
Ip: "10.0.0.10",
Port: 8848,
ServiceName: "demo.go",
GroupName: "group-a",
ClusterName: "cluster-a",
Weight: 10,
Enable: true,
Healthy: true,
@ -87,129 +69,53 @@ func main() {
Metadata: map[string]string{"idc": "shanghai"},
})
//Register with cluster name
//GroupName=DEFAULT_GROUP
ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
ClusterName: "cluster-a",
Enable: true,
Healthy: true,
Ephemeral: true,
})
//Register different cluster
//GroupName=DEFAULT_GROUP
ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{
Ip: "10.0.0.12",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
ClusterName: "cluster-b",
Enable: true,
Healthy: true,
Ephemeral: true,
})
//Register different group
ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{
Ip: "10.0.0.13",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
ClusterName: "cluster-b",
GroupName: "group-a",
Enable: true,
Healthy: true,
Ephemeral: true,
})
ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{
Ip: "10.0.0.14",
Port: 8848,
ServiceName: "demo.go",
Weight: 10,
ClusterName: "cluster-b",
GroupName: "group-b",
Enable: true,
Healthy: true,
Ephemeral: true,
})
//DeRegister with ip,port,serviceName
//ClusterName=DEFAULT, GroupName=DEFAULT_GROUP
//Note:ip=10.0.0.10,port=8848 should belong to the cluster of DEFAULT and the group of DEFAULT_GROUP.
//DeRegister
ExampleServiceClient_DeRegisterServiceInstance(client, vo.DeregisterInstanceParam{
Ip: "10.0.0.10",
Port: 8848,
ServiceName: "demo.go",
Ephemeral: true, //it must be true
})
//DeRegister with ip,port,serviceName,cluster
//GroupName=DEFAULT_GROUP
//Note:ip=10.0.0.10,port=8848,cluster=cluster-a should belong to the group of DEFAULT_GROUP.
ExampleServiceClient_DeRegisterServiceInstance(client, vo.DeregisterInstanceParam{
Ip: "10.0.0.11",
Port: 8848,
ServiceName: "demo.go",
GroupName: "group-a",
Cluster: "cluster-a",
Ephemeral: true, //it must be true
})
//DeRegister with ip,port,serviceName,cluster,group
ExampleServiceClient_DeRegisterServiceInstance(client, vo.DeregisterInstanceParam{
Ip: "10.0.0.14",
//Register
ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{
Ip: "10.0.0.10",
Port: 8848,
ServiceName: "demo.go",
Cluster: "cluster-b",
GroupName: "group-b",
Ephemeral: true, //it must be true
GroupName: "group-a",
ClusterName: "cluster-a",
Weight: 10,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc": "shanghai"},
})
//Get service with serviceName
//ClusterName=DEFAULT, GroupName=DEFAULT_GROUP
ExampleServiceClient_GetService(client, vo.GetServiceParam{
ServiceName: "demo.go",
})
//Get service with serviceName and cluster
//GroupName=DEFAULT_GROUP
ExampleServiceClient_GetService(client, vo.GetServiceParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-a", "cluster-b"},
})
//Get service with serviceName ,group
//ClusterName=DEFAULT
time.Sleep(1 * time.Second)
//Get service with serviceName, groupName , clusters
ExampleServiceClient_GetService(client, vo.GetServiceParam{
ServiceName: "demo.go",
GroupName: "group-a",
})
//SelectAllInstance return all instances,include healthy=false,enable=false,weight<=0
//ClusterName=DEFAULT, GroupName=DEFAULT_GROUP
ExampleServiceClient_SelectAllInstances(client, vo.SelectAllInstancesParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-a"},
})
//SelectAllInstance
//GroupName=DEFAULT_GROUP
ExampleServiceClient_SelectAllInstances(client, vo.SelectAllInstancesParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-a", "cluster-b"},
})
//SelectAllInstance
//ClusterName=DEFAULT
ExampleServiceClient_SelectAllInstances(client, vo.SelectAllInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a",
Clusters: []string{"cluster-a"},
})
//SelectInstances only return the instances of healthy=${HealthyOnly},enable=true and weight>0
//ClusterName=DEFAULT,GroupName=DEFAULT_GROUP
ExampleServiceClient_SelectInstances(client, vo.SelectInstancesParam{
ServiceName: "demo.go",
GroupName: "group-a",
Clusters: []string{"cluster-a"},
})
//SelectOneHealthyInstance return one instance by WRR strategy for load balance
@ -217,38 +123,51 @@ func main() {
//ClusterName=DEFAULT,GroupName=DEFAULT_GROUP
ExampleServiceClient_SelectOneHealthyInstance(client, vo.SelectOneHealthInstanceParam{
ServiceName: "demo.go",
GroupName: "group-a",
Clusters: []string{"cluster-a"},
})
//Subscribe key=serviceName+groupName+cluster
//Note:We call add multiple SubscribeCallback with the same key.
param := &vo.SubscribeParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-b"},
SubscribeCallback: func(services []model.SubscribeService, err error) {
fmt.Printf("callback111 return services:%s \n\n", util.ToJsonString(services))
GroupName: "group-a",
SubscribeCallback: func(services []model.Instance, err error) {
fmt.Printf("callback return services:%s \n\n", util.ToJsonString(services))
},
}
ExampleServiceClient_Subscribe(client, param)
param2 := &vo.SubscribeParam{
ServiceName: "demo.go",
Clusters: []string{"cluster-b"},
SubscribeCallback: func(services []model.SubscribeService, err error) {
fmt.Printf("callback222 return services:%s \n\n", util.ToJsonString(services))
},
}
ExampleServiceClient_Subscribe(client, param2)
ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{
Ip: "10.0.0.112",
Ip: "10.0.0.10",
Port: 8848,
ServiceName: "demo.go",
GroupName: "group-a",
ClusterName: "cluster-a",
Weight: 10,
ClusterName: "cluster-b",
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc": "beijing"},
})
//wait for client pull change from server
time.Sleep(10 * time.Second)
time.Sleep(3 * time.Second)
ExampleServiceClient_UpdateServiceInstance(client, vo.UpdateInstanceParam{
Ip: "10.0.0.11", //update ip
Port: 8848,
ServiceName: "demo.go",
GroupName: "group-a",
ClusterName: "cluster-a",
Weight: 10,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{"idc": "beijing1"}, //update metadata
})
//wait for client pull change from server
time.Sleep(3 * time.Second)
//Now we just unsubscribe callback1, and callback2 will still receive change event
ExampleServiceClient_UnSubscribe(client, param)
@ -260,7 +179,7 @@ func main() {
Cluster: "cluster-b",
})
//wait for client pull change from server
time.Sleep(10 * time.Second)
time.Sleep(3 * time.Second)
//GeAllService will get the list of service name
//NameSpace default value is public.If the client set the namespaceId, NameSpace will use it.
@ -269,10 +188,4 @@ func main() {
PageNo: 1,
PageSize: 10,
})
ExampleServiceClient_GetAllService(client, vo.GetAllServiceInfoParam{
NameSpace: "0e83cc81-9d8c-4bb8-a28a-ff703187543f",
PageNo: 1,
PageSize: 10,
})
}

View File

@ -19,38 +19,65 @@ package main
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
func ExampleServiceClient_RegisterServiceInstance(client naming_client.INamingClient, param vo.RegisterInstanceParam) {
success, _ := client.RegisterInstance(param)
success, err := client.RegisterInstance(param)
if !success || err != nil {
panic("RegisterServiceInstance failed!")
}
fmt.Printf("RegisterServiceInstance,param:%+v,result:%+v \n\n", param, success)
}
func ExampleServiceClient_DeRegisterServiceInstance(client naming_client.INamingClient, param vo.DeregisterInstanceParam) {
success, _ := client.DeregisterInstance(param)
success, err := client.DeregisterInstance(param)
if !success || err != nil {
panic("DeRegisterServiceInstance failed!")
}
fmt.Printf("DeRegisterServiceInstance,param:%+v,result:%+v \n\n", param, success)
}
func ExampleServiceClient_UpdateServiceInstance(client naming_client.INamingClient, param vo.UpdateInstanceParam) {
success, err := client.UpdateInstance(param)
if !success || err != nil {
panic("UpdateInstance failed!")
}
fmt.Printf("UpdateServiceInstance,param:%+v,result:%+v \n\n", param, success)
}
func ExampleServiceClient_GetService(client naming_client.INamingClient, param vo.GetServiceParam) {
service, _ := client.GetService(param)
service, err := client.GetService(param)
if err != nil {
panic("GetService failed!")
}
fmt.Printf("GetService,param:%+v, result:%+v \n\n", param, service)
}
func ExampleServiceClient_SelectAllInstances(client naming_client.INamingClient, param vo.SelectAllInstancesParam) {
instances, _ := client.SelectAllInstances(param)
instances, err := client.SelectAllInstances(param)
if err != nil {
panic("SelectAllInstances failed!")
}
fmt.Printf("SelectAllInstance,param:%+v, result:%+v \n\n", param, instances)
}
func ExampleServiceClient_SelectInstances(client naming_client.INamingClient, param vo.SelectInstancesParam) {
instances, _ := client.SelectInstances(param)
instances, err := client.SelectInstances(param)
if err != nil {
panic("SelectInstances failed!")
}
fmt.Printf("SelectInstances,param:%+v, result:%+v \n\n", param, instances)
}
func ExampleServiceClient_SelectOneHealthyInstance(client naming_client.INamingClient, param vo.SelectOneHealthInstanceParam) {
instances, _ := client.SelectOneHealthyInstance(param)
fmt.Printf("SelectInstances,param:%+v, result:%+v \n\n", param, instances)
instances, err := client.SelectOneHealthyInstance(param)
if err != nil {
panic("SelectOneHealthyInstance failed!")
}
fmt.Printf("SelectOneHealthyInstance,param:%+v, result:%+v \n\n", param, instances)
}
func ExampleServiceClient_Subscribe(client naming_client.INamingClient, param *vo.SubscribeParam) {
@ -62,6 +89,9 @@ func ExampleServiceClient_UnSubscribe(client naming_client.INamingClient, param
}
func ExampleServiceClient_GetAllService(client naming_client.INamingClient, param vo.GetAllServiceInfoParam) {
service, _ := client.GetAllServicesInfo(param)
service, err := client.GetAllServicesInfo(param)
if err != nil {
panic("GetAllService failed!")
}
fmt.Printf("GetAllService,param:%+v, result:%+v \n\n", param, service)
}

23
go.mod
View File

@ -1,18 +1,19 @@
module github.com/nacos-group/nacos-sdk-go
module github.com/nacos-group/nacos-sdk-go/v2
go 1.12
go 1.15
require (
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704
github.com/buger/jsonparser v1.1.1
github.com/go-errors/errors v1.0.1
github.com/golang/mock v1.3.1
github.com/json-iterator/go v1.1.6 // indirect
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.2
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.5.1
go.uber.org/zap v1.15.0
golang.org/x/sync v0.0.0-20190423024810-112230192c58
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
github.com/prometheus/client_golang v1.12.2
github.com/stretchr/testify v1.7.0
go.uber.org/zap v1.21.0
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
google.golang.org/grpc v1.48.0
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)

528
go.sum
View File

@ -1,95 +1,525 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 h1:PpfENOj/vPfhhy9N2OFRjpue0hjM5XqAp2thFmkXXIk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -24,7 +24,7 @@ import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
vo "github.com/nacos-group/nacos-sdk-go/vo"
vo "github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// MockIConfigClient is a mock of IConfigClient interface

View File

@ -23,9 +23,10 @@ package mock
import (
reflect "reflect"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/http_agent"
gomock "github.com/golang/mock/gomock"
constant "github.com/nacos-group/nacos-sdk-go/common/constant"
http_agent "github.com/nacos-group/nacos-sdk-go/common/http_agent"
)
// MockINacosClient is a mock of INacosClient interface

View File

@ -24,8 +24,8 @@ import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
model "github.com/nacos-group/nacos-sdk-go/model"
vo "github.com/nacos-group/nacos-sdk-go/vo"
model "github.com/nacos-group/nacos-sdk-go/v2/model"
vo "github.com/nacos-group/nacos-sdk-go/v2/vo"
)
// MockINamingClient is a mock of INamingClient interface

View File

@ -31,3 +31,16 @@ type ConfigPage struct {
PagesAvailable int `param:"pagesAvailable"`
PageItems []ConfigItem `param:"pageItems"`
}
type ConfigListenContext struct {
Group string `json:"group"`
Md5 string `json:"md5"`
DataId string `json:"dataId"`
Tenant string `json:"tenant"`
}
type ConfigContext struct {
Group string `json:"group"`
DataId string `json:"dataId"`
Tenant string `json:"tenant"`
}

View File

@ -16,9 +16,7 @@
package model
import (
"time"
)
import "time"
const (
StateRunning = iota
@ -26,31 +24,32 @@ const (
)
type Instance struct {
Valid bool `json:"valid"`
Marked bool `json:"marked"`
InstanceId string `json:"instanceId"`
Port uint64 `json:"port"`
Ip string `json:"ip"`
Weight float64 `json:"weight"`
Metadata map[string]string `json:"metadata"`
ClusterName string `json:"clusterName"`
ServiceName string `json:"serviceName"`
Enable bool `json:"enabled"`
Healthy bool `json:"healthy"`
Ephemeral bool `json:"ephemeral"`
InstanceId string `json:"instanceId"`
Ip string `json:"ip"`
Port uint64 `json:"port"`
Weight float64 `json:"weight"`
Healthy bool `json:"healthy"`
Enable bool `json:"enabled"`
Ephemeral bool `json:"ephemeral"`
ClusterName string `json:"clusterName"`
ServiceName string `json:"serviceName"`
Metadata map[string]string `json:"metadata"`
InstanceHeartBeatInterval int `json:"instanceHeartBeatInterval"`
IpDeleteTimeout int `json:"ipDeleteTimeout"`
InstanceHeartBeatTimeOut int `json:"instanceHeartBeatTimeOut"`
}
type Service struct {
Dom string `json:"dom"`
CacheMillis uint64 `json:"cacheMillis"`
UseSpecifiedURL bool `json:"useSpecifiedUrl"`
Hosts []Instance `json:"hosts"`
Checksum string `json:"checksum"`
LastRefTime uint64 `json:"lastRefTime"`
Env string `json:"env"`
Clusters string `json:"clusters"`
Metadata map[string]string `json:"metadata"`
Name string `json:"name"`
CacheMillis uint64 `json:"cacheMillis"`
Hosts []Instance `json:"hosts"`
Checksum string `json:"checksum"`
LastRefTime uint64 `json:"lastRefTime"`
Clusters string `json:"clusters"`
Name string `json:"name"`
GroupName string `json:"groupName"`
Valid bool `json:"valid"`
AllIPs bool `json:"allIPs"`
ReachProtectionThreshold bool `json:"reachProtectionThreshold"`
}
type ServiceDetail struct {
@ -86,19 +85,6 @@ type ClusterHealthChecker struct {
Type string `json:"type"`
}
type SubscribeService struct {
ClusterName string `json:"clusterName"`
Enable bool `json:"enable"`
InstanceId string `json:"instanceId"`
Ip string `json:"ip"`
Metadata map[string]string `json:"metadata"`
Port uint64 `json:"port"`
ServiceName string `json:"serviceName"`
Valid bool `json:"valid"`
Weight float64 `json:"weight"`
Healthy bool `json:"healthy"`
}
type BeatInfo struct {
Ip string `json:"ip"`
Port uint64 `json:"port"`

View File

@ -19,13 +19,14 @@ package util
import (
"encoding/json"
"net"
"net/http"
"net/url"
"strconv"
"time"
"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/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/model"
)
func CurrentMillis() int64 {
@ -65,93 +66,39 @@ func GetConfigCacheKey(dataId string, group string, tenant string) string {
return dataId + constant.CONFIG_INFO_SPLITER + group + constant.CONFIG_INFO_SPLITER + tenant
}
var (
localIP = ""
privateCIDR []*net.IPNet
)
var localIP = ""
func LocalIP() string {
if localIP != "" {
return localIP
}
faces, err := getFaces()
if err != nil {
return ""
}
for _, address := range faces {
ipNet, ok := address.(*net.IPNet)
if !ok || ipNet.IP.To4() == nil || isFilteredIP(ipNet.IP) {
continue
if localIP == "" {
netInterfaces, err := net.Interfaces()
if err != nil {
logger.Errorf("get Interfaces failed,err:%+v", err)
return ""
}
localIP = ipNet.IP.String()
break
}
for i := 0; i < len(netInterfaces); i++ {
if ((netInterfaces[i].Flags & net.FlagUp) != 0) && ((netInterfaces[i].Flags & net.FlagLoopback) == 0) {
addrs, err := netInterfaces[i].Addrs()
if err != nil {
logger.Errorf("get InterfaceAddress failed,err:%+v", err)
return ""
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
localIP = ipnet.IP.String()
break
}
}
}
}
if localIP != "" {
logger.Infof("Local IP:%s", localIP)
if len(localIP) > 0 {
logger.Infof("Local IP:%s", localIP)
}
}
return localIP
}
// SetFilterNetNumberAndMask ipMask like 127.0.0.0/8
func SetFilterNetNumberAndMask(ipMask ...string) error {
var (
err error
ipNet *net.IPNet
)
for _, b := range ipMask {
_, ipNet, err = net.ParseCIDR(b)
if err != nil {
return err
}
privateCIDR = append(privateCIDR, ipNet)
}
return err
}
// getFaces return addresses from interfaces that is up
func getFaces() ([]net.Addr, error) {
var upAddrs []net.Addr
interfaces, err := net.Interfaces()
if err != nil {
logger.Errorf("get Interfaces failed,err:%+v", err)
return nil, err
}
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 {
continue
}
if (iface.Flags & net.FlagLoopback) != 0 {
continue
}
addresses, err := iface.Addrs()
if err != nil {
logger.Errorf("get InterfaceAddress failed,err:%+v", err)
return nil, err
}
upAddrs = append(upAddrs, addresses...)
}
return upAddrs, nil
}
func isFilteredIP(ip net.IP) bool {
for _, privateIP := range privateCIDR {
if privateIP.Contains(ip) {
return true
}
}
return false
}
func GetDurationWithDefault(metadata map[string]string, key string, defaultDuration time.Duration) time.Duration {
data, ok := metadata[key]
if ok {
@ -174,6 +121,17 @@ func GetUrlFormedMap(source map[string]string) (urlEncoded string) {
return
}
// get status code by response,default is NA
func GetStatusCode(response *http.Response) string {
var statusCode string
if response != nil {
statusCode = strconv.Itoa(response.StatusCode)
} else {
statusCode = "NA"
}
return statusCode
}
func DeepCopyMap(params map[string]string) map[string]string {
result := make(map[string]string, len(params))
for k, v := range params {

View File

@ -1,51 +0,0 @@
package util
import (
"net"
"testing"
)
func TestIsPrivateIP(t *testing.T) {
if err := SetFilterNetNumberAndMask([]string{
"10.0.0.0/8", // RFC 1918 IPv4 private network address
"100.64.0.0/10", // RFC 6598 IPv4 shared address space
"127.0.0.0/8", // RFC 1122 IPv4 loopback address
"169.254.0.0/16", // RFC 3927 IPv4 link local address
"172.16.0.0/12", // RFC 1918 IPv4 private network address
"192.0.0.0/24", // RFC 6890 IPv4 IANA address
"192.0.2.0/24", // RFC 5737 IPv4 documentation address
"192.168.0.0/16", // RFC 1918 IPv4 private network address
}...); err != nil {
t.Fatal(err)
}
tests := []struct {
ip string
private bool
}{
// IPv4 private addresses
{"10.0.0.1", true}, // private network address
{"100.64.0.1", true}, // shared address space
{"172.16.0.1", true}, // private network address
{"192.168.0.1", true}, // private network address
{"192.0.0.1", true}, // IANA address
{"192.0.2.1", true}, // documentation address
{"127.0.0.1", true}, // loopback address
{"127.1.0.1", true}, // loopback address
{"169.254.0.1", true}, // link local address
// IPv4 public addresses
{"1.2.3.4", false},
}
for _, tt := range tests {
t.Run(tt.ip, func(t *testing.T) {
ip := net.ParseIP(tt.ip)
if ip == nil {
t.Fatalf("%s is not a valid ip address", tt.ip)
}
if got, want := isFilteredIP(ip), tt.private; got != want {
t.Fatalf("got %v for %v want %v", got, ip, want)
}
})
}
}

29
util/content.go Normal file
View File

@ -0,0 +1,29 @@
package util
/*
* 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.
*/
const SHOW_CONTENT_SIZE = 100
func TruncateContent(content string) string {
if content == "" {
return ""
}
if len(content) <= SHOW_CONTENT_SIZE {
return content
}
return content[0:SHOW_CONTENT_SIZE]
}

View File

@ -22,7 +22,7 @@ import (
"strconv"
"strings"
"github.com/nacos-group/nacos-sdk-go/common/logger"
"github.com/nacos-group/nacos-sdk-go/v2/common/logger"
)
func TransformObject2Param(object interface{}) (params map[string]string) {

View File

@ -1,6 +1,21 @@
/*
* 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 vo
import "github.com/nacos-group/nacos-sdk-go/common/constant"
import "github.com/nacos-group/nacos-sdk-go/v2/common/constant"
type NacosClientParam struct {
ClientConfig *constant.ClientConfig // optional

View File

@ -16,30 +16,22 @@
package vo
type ConfigType string
const (
PROPERTIES ConfigType = "properties"
XML ConfigType = "xml"
JSON ConfigType = "json"
TEXT ConfigType = "text"
HTML ConfigType = "html"
YAML ConfigType = "yaml"
)
type Listener func(namespace, group, dataId, data string)
type ConfigParam struct {
DataId string `param:"dataId"` //required
Group string `param:"group"` //required
Content string `param:"content"` //required
DatumId string `param:"datumId"`
Type ConfigType `param:"type"`
OnChange func(namespace, group, dataId, data string)
DataId string `param:"dataId"` //required
Group string `param:"group"` //required
Content string `param:"content"` //required
Tag string `param:"tag"`
AppName string `param:"appName"`
BetaIps string `param:"betaIps"`
CasMd5 string `param:"casMd5"`
Type string `param:"type"`
EncryptedDataKey string `param:"encryptedDataKey"`
OnChange func(namespace, group, dataId, data string)
}
type SearchConfigParam struct {
type SearchConfigParm struct {
Search string `param:"search"`
DataId string `param:"dataId"`
Group string `param:"group"`

View File

@ -16,7 +16,7 @@
package vo
import "github.com/nacos-group/nacos-sdk-go/model"
import "github.com/nacos-group/nacos-sdk-go/v2/model"
type RegisterInstanceParam struct {
Ip string `param:"ip"` //required
@ -41,15 +41,16 @@ type DeregisterInstanceParam struct {
}
type UpdateInstanceParam struct {
Ip string `param:"ip"` // required
Port uint64 `param:"port"` // required
ClusterName string `param:"cluster"` // optional,default:DEFAULT
ServiceName string `param:"serviceName"` // required
GroupName string `param:"groupName"` // optional,default:DEFAULT_GROUP
Ephemeral bool `param:"ephemeral"` // optional
Weight float64 `param:"weight"` // required,it must be lager than 0
Enable bool `param:"enabled"` // required,the instance can be access or not
Metadata map[string]string `param:"metadata"` // optional
Ip string `param:"ip"` //required
Port uint64 `param:"port"` //required
Weight float64 `param:"weight"` //required,it must be lager than 0
Enable bool `param:"enabled"` //required,the instance can be access or not
Healthy bool `param:"healthy"` //required,the instance is health or not
Metadata map[string]string `param:"metadata"` //optional
ClusterName string `param:"clusterName"` //optional,default:DEFAULT
ServiceName string `param:"serviceName"` //required
GroupName string `param:"groupName"` //optional,default:DEFAULT_GROUP
Ephemeral bool `param:"ephemeral"` //optional
}
type GetServiceParam struct {
@ -66,10 +67,10 @@ type GetAllServiceInfoParam struct {
}
type SubscribeParam struct {
ServiceName string `param:"serviceName"` //required
Clusters []string `param:"clusters"` //optional,default:DEFAULT
GroupName string `param:"groupName"` //optional,default:DEFAULT_GROUP
SubscribeCallback func(services []model.SubscribeService, err error) //required
ServiceName string `param:"serviceName"` //required
Clusters []string `param:"clusters"` //optional,default:DEFAULT
GroupName string `param:"groupName"` //optional,default:DEFAULT_GROUP
SubscribeCallback func(services []model.Instance, err error) //required
}
type SelectAllInstancesParam struct {