diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 75087e41..22a9cd52 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -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 diff --git a/README.md b/README.md index 69a2cd9b..33772383 100644 --- a/README.md +++ b/README.md @@ -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 instance:RegisterInstance @@ -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 event:CancelListenConfig ```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. diff --git a/README_CN.md b/README_CN.md index 40b2fb0f..830cc272 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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, diff --git a/api/grpc/nacos_grpc_service.pb.go b/api/grpc/nacos_grpc_service.pb.go new file mode 100644 index 00000000..a11363a1 --- /dev/null +++ b/api/grpc/nacos_grpc_service.pb.go @@ -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", +} diff --git a/api/proto/nacos_grpc_service.proto b/api/proto/nacos_grpc_service.proto new file mode 100644 index 00000000..602e7992 --- /dev/null +++ b/api/proto/nacos_grpc_service.proto @@ -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 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) { + } +} diff --git a/clients/cache/disk_cache.go b/clients/cache/disk_cache.go index bc51b15d..6cfe4c94 100644 --- a/clients/cache/disk_cache.go +++ b/clients/cache/disk_cache.go @@ -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) +} diff --git a/clients/cache/disk_cache_test.go b/clients/cache/disk_cache_test.go index bbbabee0..77af0fe6 100644 --- a/clients/cache/disk_cache_test.go +++ b/clients/cache/disk_cache_test.go @@ -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" -) -func TestGetFileName(t *testing.T) { + "github.com/nacos-group/nacos-sdk-go/v2/common/file" +) - name := GetFileName("nacos@@providers:org.apache.dubbo.UserProvider:hangzhou", "tmp") +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) + }) +} - 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") - } +// write file content +func writeFileContent(filepath, content string) error { + return ioutil.WriteFile(filepath, []byte(content), 0666) } diff --git a/clients/client_factory.go b/clients/client_factory.go index a623bd75..67fafe65 100644 --- a/clients/client_factory.go +++ b/clients/client_factory.go @@ -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 diff --git a/clients/client_factory_test.go b/clients/client_factory_test.go index 9a17cd1b..f3b8eb5f 100644 --- a/clients/client_factory_test.go +++ b/clients/client_factory_test.go @@ -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)) }) - } diff --git a/clients/config_client/config_client.go b/clients/config_client/config_client.go index 6f6a7d53..9ed9a95f 100644 --- a/clients/config_client/config_client.go +++ b/clients/config_client/config_client.go @@ -17,57 +17,62 @@ package config_client import ( - "errors" "fmt" "math" - "net/url" "os" - "strconv" "strings" "sync" - "sync/atomic" "time" - "github.com/aliyun/alibaba-cloud-sdk-go/services/kms" + "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/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/common/monitor" -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 -} + "github.com/aliyun/alibaba-cloud-sdk-go/services/kms" + "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" +) 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 - } - 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, - } - err = logger.InitLogger(loggerConfig) - if err != nil { - return config, err + return nil, err + } + + if err = initLogger(clientConfig); err != nil { + return nil, err + } + clientConfig.CacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config" + config.configCacheDir = clientConfig.CacheDir + + if config.configProxy, err = 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 } - return config, err -} -func (client *ConfigClient) sync() (clientConfig constant.ClientConfig, - serverConfigs []constant.ServerConfig, agent http_agent.IHttpAgent, err error) { - clientConfig, err = client.GetClientConfig() + uid, err := uuid.NewV4() 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 + return nil, err } + config.uid = uid.String() - agent, err = client.GetHttpAgent() - if err != nil { - logger.Errorf("getHttpAgent catch error:%+v", err) - } - return + 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 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,214 +314,172 @@ 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) - } +func (client *ConfigClient) SearchConfig(param vo.SearchConfigParm) (*model.ConfigPage, error) { + return client.searchConfigInner(param) } -// 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)) +func (client *ConfigClient) CloseClient() { + client.configProxy.getRpcClient(client).Shutdown() +} + +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") + } + if param.PageNo <= 0 { + param.PageNo = 1 + } + if param.PageSize <= 0 { + param.PageSize = 10 + } + clientConfig, _ := client.GetClientConfig() + configItems, err := client.configProxy.searchConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey) + if err != nil { + logger.Errorf("search config from server error:%+v ", err) + if _, ok := err.(*nacos_error.NacosError); ok { + nacosErr := err.(*nacos_error.NacosError) + if nacosErr.ErrorCode() == "404" { + return nil, errors.New("config not found") } - 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) - } + if nacosErr.ErrorCode() == "403" { + return nil, errors.New("get config forbidden") } - atomic.StoreInt32(&client.currentTaskCount, int32(taskCount)) } - return nil + return nil, err } + return configItems, 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 - } - } +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) } - if len(listeningConfigs) > 0 { - clientConfig, err := client.GetClientConfig() - if err != nil { - logger.Errorf("[checkConfigInfo.GetClientConfig] err: %+v", err) - return err + }() +} + +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 } - // 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 + if !needAllSync { + continue } - for _, v := range initializationList { - v.isInitializing = false - client.cacheMap.Set(util.GetConfigCacheKey(v.dataId, v.group, v.tenant), v) + } + 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 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) + if iResponse == nil && !iResponse.IsSuccess() { + continue } - } - 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 + 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) + } + } } - 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) + + 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 } } } } -} - -func (client *ConfigClient) buildBasePath(serverConfig constant.ServerConfig) (basePath string) { - basePath = "http://" + serverConfig.IpAddr + ":" + - strconv.FormatUint(serverConfig.Port, 10) + serverConfig.ContextPath + constant.CONFIG_PATH - return -} + if needAllSync { + client.lastAllSyncTime = time.Now() + } -func (client *ConfigClient) SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error) { - return client.searchConfigInner(param) + if hasChangedKeys { + client.notifyListenConfig() + } + monitor.GetListenConfigCountMonitor().Set(float64(client.cacheMap.Count())) } -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") +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}) } - 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) + return request } -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") +func (client *ConfigClient) refreshContentAndCheck(cacheData *cacheData, notify bool) { + configQueryResponse, err := client.configProxy.queryConfig(cacheData.dataId, cacheData.group, cacheData.tenant, + constant.DEFAULT_TIMEOUT_MILLS, notify, client) + if err != nil { + logger.Errorf("refresh content and check md5 fail ,dataId=%s,group=%s,tenant=%s ", cacheData.dataId, + cacheData.group, cacheData.tenant) + return } - if len(param.Content) <= 0 { - err = errors.New("[client.DeleteAggr] param.content can not be empty") + 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) } - if len(param.DatumId) <= 0 { - err = errors.New("[client.DeleteAggr] param.DatumId can not be empty") + 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) } - clientConfig, _ := client.GetClientConfig() - return client.configProxy.DeleteAggProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey) } -func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*model.ConfigPage, error) { - if param.Search != "accurate" && param.Search != "blur" { - return nil, errors.New("[client.searchConfigInner] param.search must be accurate or blur") - } - if param.PageNo <= 0 { - param.PageNo = 1 - } - if param.PageSize <= 0 { - param.PageSize = 10 - } - clientConfig, _ := client.GetClientConfig() - configItems, err := client.configProxy.SearchConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey) - if err != nil { - logger.Errorf("search config from server error:%+v ", err) - if _, ok := err.(*nacos_error.NacosError); ok { - nacosErr := err.(*nacos_error.NacosError) - if nacosErr.ErrorCode() == "404" { - return nil, nil - } - if nacosErr.ErrorCode() == "403" { - return nil, errors.New("get config forbidden") - } - } - return nil, err - } - return configItems, nil +func (client *ConfigClient) notifyListenConfig() { + client.listenExecute <- struct{}{} } diff --git a/clients/config_client/config_client_interface.go b/clients/config_client/config_client_interface.go index 4fd6ad1b..b50ab106 100644 --- a/clients/config_client/config_client_interface.go +++ b/clients/config_client/config_client_interface.go @@ -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() } diff --git a/clients/config_client/config_client_test.go b/clients/config_client/config_client_test.go index c6867f75..5e5c7329 100644 --- a/clients/config_client/config_client_test.go +++ b/clients/config_client/config_client_test.go @@ -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/stretchr/testify/assert" -) -var goVersion = runtime.Version() - -var clientConfigTest = constant.ClientConfig{ - TimeoutMs: 10000, - ListenInterval: 20000, - BeatInterval: 10000, -} + "github.com/nacos-group/nacos-sdk-go/v2/util" -var clientConfigTestWithTenant = constant.ClientConfig{ - TimeoutMs: 10000, - ListenInterval: 20000, - BeatInterval: 10000, - NamespaceId: "tenant", -} + "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" -var serverConfigTest = constant.ServerConfig{ - ContextPath: "/nacos", - Port: 80, - IpAddr: "console.nacos.io", -} + "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 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", +func createConfigClientTest() *ConfigClient { + nc := nacos_client.NacosClient{} + _ = nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions}) + _ = nc.SetClientConfig(*clientConfigWithOptions) + _ = nc.SetHttpAgent(&http_agent.HttpAgent{}) + client, _ := NewConfigClient(&nc) + client.configProxy = &MockConfigProxy{} + return client } -var client *ConfigClient -func TestMain(m *testing.M) { - nc := nacos_client.NacosClient{} - nc.SetServerConfig([]constant.ServerConfig{*serverConfigWithOptions}) - nc.SetClientConfig(*clientConfigWithOptions) - nc.SetHttpAgent(&http_agent.HttpAgent{}) - client, _ = NewConfigClient(&nc) - m.Run() +type MockConfigProxy struct { } -func createConfigClientHttpTest(mockHttpAgent http_agent.IHttpAgent) *ConfigClient { - nc := nacos_client.NacosClient{} - nc.SetServerConfig([]constant.ServerConfig{serverConfigTest}) - nc.SetClientConfig(clientConfigTest) - nc.SetHttpAgent(mockHttpAgent) - client, _ := NewConfigClient(&nc) - return client +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) { - success, err := client.PublishConfig(vo.ConfigParam{ - DataId: dataIdKey, - Group: "group", - Content: "hello world2!"}) - assert.Nil(t, err) - assert.True(t, success) -} + client := createConfigClientTest() -func Test_PublishConfigWithType(t *testing.T) { success, err := client.PublishConfig(vo.ConfigParam{ - DataId: dataIdKey, + DataId: localConfigTest.DataId, Group: "group", - Content: "foo", - Type: vo.YAML, - }) + Content: "hello world"}) assert.Nil(t, err) assert.True(t, success) } -func Test_PublishConfigWithErrorResponse(t *testing.T) { - controller := gomock.NewController(t) - defer controller.Finish() - - mockHttpAgent := mock.NewMockIHttpAgent(controller) - 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) - 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) +// CancelListenConfig +func TestCancelListenConfig(t *testing.T) { + //Multiple listeners listen for different configurations, cancel one + t.Run("TestMultipleListenersCancelOne", func(t *testing.T) { + client := createConfigClientTest() + var err error listenConfigParam := vo.ConfigParam{ - DataId: 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) - - 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(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") - } + _ = client.ListenConfig(listenConfigParam1) + err = client.CancelListenConfig(listenConfigParam) + assert.Nil(t, err) }) } - -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") -} diff --git a/clients/config_client/config_proxy.go b/clients/config_client/config_proxy.go index 84a85332..830c83c8 100644 --- a/clients/config_client/config_proxy.go +++ b/clients/config_client/config_proxy.go @@ -18,18 +18,26 @@ package config_client import ( "encoding/json" - "errors" "net/http" "strconv" - "strings" - - "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" + "time" + + "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) - if err != nil { - return false, errors.New("[client.PublishConfig] publish config failed:" + err.Error()) - } - if strings.ToLower(strings.Trim(result, " ")) == "true" { - return true, nil - } else { - return false, errors.New("[client.PublishConfig] publish config failed:" + result) +func (cp *ConfigProxy) queryConfig(dataId, group, tenant string, timeout uint64, notify bool, client *ConfigClient) (*rpc_response.ConfigQueryResponse, error) { + if group == "" { + group = constant.DEFAULT_GROUP } -} - -func (cp *ConfigProxy) PublishAggProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) { - params := util.TransformObject2Param(param) - if len(tenant) > 0 { - params["tenant"] = tenant + 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") } - 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) + iResponse, err := cp.requestProxy(cp.getRpcClient(client), configQueryRequest, timeout) if err != nil { - return false, errors.New("[client.PublishAggProxy] publish agg failed:" + err.Error()) + return nil, err } - return true, nil -} - -func (cp *ConfigProxy) DeleteAggProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) { - params := util.TransformObject2Param(param) - if len(tenant) > 0 { - params["tenant"] = tenant + response, ok := iResponse.(*rpc_response.ConfigQueryResponse) + if !ok { + return nil, errors.New("ConfigQueryRequest returns type error") } - 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()) + 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 } - return true, nil -} -func (cp *ConfigProxy) DeleteConfigProxy(param vo.ConfigParam, tenant, accessKey, secretKey string) (bool, error) { - params := util.TransformObject2Param(param) - if len(tenant) > 0 { - params["tenant"] = tenant + if response.GetErrorCode() == 300 { + //todo LocalConfigInfoProcessor.saveSnapshot + cache.WriteConfigToFile(cacheKey, cp.clientConfig.CacheDir, "") + //todo LocalConfigInfoProcessor.saveEncryptDataKeySnapshot + return response, nil } - 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 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 strings.ToLower(strings.Trim(result, " ")) == "true" { - return true, nil - } else { - return false, errors.New("[client.DeleteConfig] deleted config failed: " + string(result)) + + 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) ListenConfig(params map[string]string, isInitializing bool, tenant, accessKey, secretKey string) (string, error) { - //fixed at 30000ms,avoid 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), +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, } - if isInitializing { - headers["Long-Pulling-Timeout-No-Hangup"] = "true" + + 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 rpcClient +} - headers["accessKey"] = accessKey - headers["secretKey"] = secretKey +func (cp *ConfigProxy) getRpcClient(client *ConfigClient) *rpc.RpcClient { + return cp.createRpcClient("0", client) +} - if len(tenant) > 0 { - params["tenant"] = tenant - } - logger.Infof("[client.ListenConfig] request params:%+v header:%+v \n", params, headers) - // In order to prevent the server from handling the delay of the client's long task, - // increase the client's read timeout to avoid this problem. - timeout := listenInterval + listenInterval/10 - result, err := cp.nacosServer.ReqConfigApi(constant.CONFIG_LISTEN_PATH, params, headers, http.MethodPost, timeout) - return result, err +type ConfigChangeNotifyRequestHandler struct { + client *ConfigClient +} + +func (c *ConfigChangeNotifyRequestHandler) Name() string { + return "ConfigChangeNotifyRequestHandler" +} + +func (c *ConfigChangeNotifyRequestHandler) RequestReply(request rpc_request.IRequest, rpcClient *rpc.RpcClient) rpc_response.IResponse { + configChangeNotifyRequest, ok := request.(*rpc_request.ConfigChangeNotifyRequest) + if ok { + 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 } diff --git a/clients/config_client/config_proxy_interface.go b/clients/config_client/config_proxy_interface.go new file mode 100644 index 00000000..b241307b --- /dev/null +++ b/clients/config_client/config_proxy_interface.go @@ -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 +} diff --git a/clients/config_client/limiter.go b/clients/config_client/limiter.go new file mode 100644 index 00000000..06cc1e51 --- /dev/null +++ b/clients/config_client/limiter.go @@ -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) +} diff --git a/clients/config_client/limiter_test.go b/clients/config_client/limiter_test.go new file mode 100644 index 00000000..f64128ae --- /dev/null +++ b/clients/config_client/limiter_test.go @@ -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) + } + } +} diff --git a/clients/nacos_client/nacos_client.go b/clients/nacos_client/nacos_client.go index 0f0f7380..f2a7ca88 100644 --- a/clients/nacos_client/nacos_client.go +++ b/clients/nacos_client/nacos_client.go @@ -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 diff --git a/clients/nacos_client/nacos_client_interface.go b/clients/nacos_client/nacos_client_interface.go index fca7c616..d9c9b113 100644 --- a/clients/nacos_client/nacos_client_interface.go +++ b/clients/nacos_client/nacos_client_interface.go @@ -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 diff --git a/clients/naming_client/host_reactor.go b/clients/naming_client/host_reactor.go deleted file mode 100644 index 9a5eb4a7..00000000 --- a/clients/naming_client/host_reactor.go +++ /dev/null @@ -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)) -} diff --git a/clients/naming_client/naming_cache/service_info_holder.go b/clients/naming_client/naming_cache/service_info_holder.go new file mode 100644 index 00000000..3cbbfd57 --- /dev/null +++ b/clients/naming_client/naming_cache/service_info_holder.go @@ -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)) +} diff --git a/clients/naming_client/host_reactor_test.go b/clients/naming_client/naming_cache/service_info_holder_test.go similarity index 59% rename from clients/naming_client/host_reactor_test.go rename to clients/naming_client/naming_cache/service_info_holder_test.go index ebc0f202..3e1ce1ec 100644 --- a/clients/naming_client/host_reactor_test.go +++ b/clients/naming_client/naming_cache/service_info_holder_test.go @@ -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{ { diff --git a/clients/naming_client/naming_cache/subscribe_callback.go b/clients/naming_client/naming_cache/subscribe_callback.go new file mode 100644 index 00000000..9f4bb903 --- /dev/null +++ b/clients/naming_client/naming_cache/subscribe_callback.go @@ -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) + } + } +} diff --git a/clients/naming_client/subscribe_callback_test.go b/clients/naming_client/naming_cache/subscribe_callback_test.go similarity index 63% rename from clients/naming_client/subscribe_callback_test.go rename to clients/naming_client/naming_cache/subscribe_callback_test.go index 53e26797..fce3354a 100644 --- a/clients/naming_client/subscribe_callback_test.go +++ b/clients/naming_client/naming_cache/subscribe_callback_test.go @@ -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, ","), ¶m.SubscribeCallback) + ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), ¶m.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], ¶m.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, ","), ¶m.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, ","), ¶m.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, ","), ¶m2.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, ","), ¶m2.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, ","), ¶m2.SubscribeCallback) + ed.RemoveCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), ¶m2.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], ¶m.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, ","), ¶m.SubscribeCallback) + ed.AddCallbackFunc(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), ¶m.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, ","), ¶m2.SubscribeCallback) - - ed.ServiceChanged(&service) + ed.AddCallbackFunc(util.GetGroupName(param2.ServiceName, param2.GroupName), strings.Join(param2.Clusters, ","), ¶m2.SubscribeCallback) + cacheKey := util.GetServiceCacheKey(util.GetGroupName(service.Name, service.GroupName), service.Clusters) + ed.ServiceChanged(cacheKey, &service) } diff --git a/clients/naming_client/naming_client.go b/clients/naming_client/naming_client.go index 5eb1ec6c..89e2c4ef 100644 --- a/clients/naming_client/naming_client.go +++ b/clients/naming_client/naming_client.go @@ -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, - } - err = logger.InitLogger(loggerConfig) - if err != nil { + + if err = initLogger(clientConfig); 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 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 } - 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, - } + clusters := strings.Join(param.Clusters, ",") + sc.serviceInfoHolder.RegisterCallback(util.GetGroupName(param.ServiceName, param.GroupName), clusters, ¶m.SubscribeCallback) + _, err := sc.serviceProxy.Subscribe(param.ServiceName, param.GroupName, clusters) + return err +} - sc.subCallback.AddCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), ¶m.SubscribeCallback) - svc, err := sc.GetService(serviceParam) - if err != nil { - return err +// 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, ¶m.SubscribeCallback) + if sc.serviceInfoHolder.IsSubscribed(serviceFullName, clusters) { + err = sc.serviceProxy.Unsubscribe(param.ServiceName, param.GroupName, clusters) } - if !sc.hostReactor.serviceProxy.clientConfig.NotLoadCacheAtStart { - sc.subCallback.ServiceChanged(&svc) - } - return nil + + 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, ","), ¶m.SubscribeCallback) - return nil +// CloseClient ... +func (sc *NamingClient) CloseClient() { + sc.serviceProxy.CloseClient() } diff --git a/clients/naming_client/naming_client_interface.go b/clients/naming_client/naming_client_interface.go index e1175a42..51376da0 100644 --- a/clients/naming_client/naming_client_interface.go +++ b/clients/naming_client/naming_client_interface.go @@ -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() } diff --git a/clients/naming_client/naming_client_test.go b/clients/naming_client/naming_client_test.go index 56b72a09..428cbfa6 100644 --- a/clients/naming_client/naming_client_test.go +++ b/clients/naming_client/naming_client_test.go @@ -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) } + } diff --git a/clients/naming_client/naming_grpc/connection_event_listener.go b/clients/naming_client/naming_grpc/connection_event_listener.go new file mode 100644 index 00000000..e889bd26 --- /dev/null +++ b/clients/naming_client/naming_grpc/connection_event_listener.go @@ -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)) +} diff --git a/clients/naming_client/naming_grpc/connection_event_listener_test.go b/clients/naming_client/naming_grpc/connection_event_listener_test.go new file mode 100644 index 00000000..118d388f --- /dev/null +++ b/clients/naming_client/naming_grpc/connection_event_listener_test.go @@ -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) + } +} diff --git a/clients/naming_client/naming_grpc/naming_grpc_proxy.go b/clients/naming_client/naming_grpc/naming_grpc_proxy.go new file mode 100644 index 00000000..ae8eddee --- /dev/null +++ b/clients/naming_client/naming_grpc/naming_grpc_proxy.go @@ -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() +} diff --git a/clients/naming_client/naming_grpc/naming_grpc_proxy_test.go b/clients/naming_client/naming_grpc/naming_grpc_proxy_test.go new file mode 100644 index 00000000..c94f3c00 --- /dev/null +++ b/clients/naming_client/naming_grpc/naming_grpc_proxy_test.go @@ -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) {} diff --git a/clients/naming_client/beat_reactor.go b/clients/naming_client/naming_http/beat_reactor.go similarity index 63% rename from clients/naming_client/beat_reactor.go rename to clients/naming_client/naming_http/beat_reactor.go index e40a7102..b92385c4 100644 --- a/clients/naming_client/beat_reactor.go +++ b/clients/naming_client/naming_http/beat_reactor.go @@ -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 +} diff --git a/clients/naming_client/beat_reactor_test.go b/clients/naming_client/naming_http/beat_reactor_test.go similarity index 84% rename from clients/naming_client/beat_reactor_test.go rename to clients/naming_client/naming_http/beat_reactor_test.go index 8c3aa4d3..f5fa69fc 100644 --- a/clients/naming_client/beat_reactor_test.go +++ b/clients/naming_client/naming_http/beat_reactor_test.go @@ -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) diff --git a/clients/naming_client/naming_http/naming_http_proxy.go b/clients/naming_client/naming_http/naming_http_proxy.go new file mode 100644 index 00000000..23438dec --- /dev/null +++ b/clients/naming_client/naming_http/naming_http_proxy.go @@ -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() { + +} diff --git a/clients/naming_client/push_receiver.go b/clients/naming_client/naming_http/push_receiver.go similarity index 84% rename from clients/naming_client/push_receiver.go rename to clients/naming_client/naming_http/push_receiver.go index fceefc8b..691a817f 100644 --- a/clients/naming_client/push_receiver.go +++ b/clients/naming_client/naming_http/push_receiver.go @@ -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) diff --git a/clients/naming_client/naming_instance_chooser.go b/clients/naming_client/naming_instance_chooser.go new file mode 100644 index 00000000..5750cdb2 --- /dev/null +++ b/clients/naming_client/naming_instance_chooser.go @@ -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] +} diff --git a/clients/naming_client/naming_proxy.go b/clients/naming_client/naming_proxy.go deleted file mode 100644 index 6e34c072..00000000 --- a/clients/naming_client/naming_proxy.go +++ /dev/null @@ -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) -} diff --git a/clients/naming_client/naming_proxy/proxy_interface.go b/clients/naming_client/naming_proxy/proxy_interface.go new file mode 100644 index 00000000..81e06057 --- /dev/null +++ b/clients/naming_client/naming_proxy/proxy_interface.go @@ -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() +} diff --git a/clients/naming_client/naming_proxy/proxy_interface_mock.go b/clients/naming_client/naming_proxy/proxy_interface_mock.go new file mode 100644 index 00000000..013f0c7c --- /dev/null +++ b/clients/naming_client/naming_proxy/proxy_interface_mock.go @@ -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) +} diff --git a/clients/naming_client/naming_proxy_delegate.go b/clients/naming_client/naming_proxy_delegate.go new file mode 100644 index 00000000..2a283f30 --- /dev/null +++ b/clients/naming_client/naming_proxy_delegate.go @@ -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() +} diff --git a/clients/naming_client/service_info_updater.go b/clients/naming_client/service_info_updater.go new file mode 100644 index 00000000..22e3c213 --- /dev/null +++ b/clients/naming_client/service_info_updater.go @@ -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) +} diff --git a/clients/naming_client/subscribe_callback.go b/clients/naming_client/subscribe_callback.go deleted file mode 100644 index 69353b2e..00000000 --- a/clients/naming_client/subscribe_callback.go +++ /dev/null @@ -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) - } - } -} diff --git a/common/constant/client_config_options.go b/common/constant/client_config_options.go index 5e6dd84a..94089057 100644 --- a/common/constant/client_config_options.go +++ b/common/constant/client_config_options.go @@ -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,17 +46,17 @@ func NewClientConfig(opts ...ClientOption) *ClientConfig { // ClientOption ... type ClientOption func(*ClientConfig) -// WithCustomLogger ... -func WithCustomLogger(logger logger.Logger) ClientOption { +// WithTimeoutMs ... +func WithTimeoutMs(timeoutMs uint64) ClientOption { return func(config *ClientConfig) { - config.CustomLogger = logger + config.TimeoutMs = timeoutMs } } -// WithTimeoutMs ... -func WithTimeoutMs(timeoutMs uint64) ClientOption { +// WithAppName ... +func WithAppName(appName string) ClientOption { return func(config *ClientConfig) { - config.TimeoutMs = timeoutMs + config.AppName = appName } } @@ -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 } } diff --git a/common/constant/client_config_options_test.go b/common/constant/client_config_options_test.go index 5e0a4fc3..17bdf413 100644 --- a/common/constant/client_config_options_test.go +++ b/common/constant/client_config_options_test.go @@ -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) } diff --git a/common/constant/config.go b/common/constant/config.go index 14150a90..60249d24 100644 --- a/common/constant/config.go +++ b/common/constant/config.go @@ -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 } diff --git a/common/constant/const.go b/common/constant/const.go index eee0362a..fde7cf45 100644 --- a/common/constant/const.go +++ b/common/constant/const.go @@ -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" ) diff --git a/common/constant/server_config_options.go b/common/constant/server_config_options.go index 6c927cb1..f9319cd1 100644 --- a/common/constant/server_config_options.go +++ b/common/constant/server_config_options.go @@ -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 + } +} diff --git a/common/constant/server_tls_options.go b/common/constant/server_tls_options.go new file mode 100644 index 00000000..44f6bb30 --- /dev/null +++ b/common/constant/server_tls_options.go @@ -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 + } +} diff --git a/common/constant/server_tls_options_test.go b/common/constant/server_tls_options_test.go new file mode 100644 index 00000000..2e1e5bcc --- /dev/null +++ b/common/constant/server_tls_options_test.go @@ -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) + }) +} diff --git a/common/file/file.go b/common/file/file.go index 10f3efeb..03407239 100644 --- a/common/file/file.go +++ b/common/file/file.go @@ -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 +} diff --git a/common/http_agent/delete.go b/common/http_agent/delete.go index 9bd8c402..fe8948d6 100644 --- a/common/http_agent/delete.go +++ b/common/http_agent/delete.go @@ -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 { diff --git a/common/http_agent/get.go b/common/http_agent/get.go index a0f023a0..3016a135 100644 --- a/common/http_agent/get.go +++ b/common/http_agent/get.go @@ -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 { diff --git a/common/http_agent/http_agent.go b/common/http_agent/http_agent.go index 084eb1b3..c131d072 100644 --- a/common/http_agent/http_agent.go +++ b/common/http_agent/http_agent.go @@ -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 + } diff --git a/common/http_agent/post.go b/common/http_agent/post.go index 13fffc1c..1771e45e 100644 --- a/common/http_agent/post.go +++ b/common/http_agent/post.go @@ -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) diff --git a/common/http_agent/put.go b/common/http_agent/put.go index 1eee773f..279327b0 100644 --- a/common/http_agent/put.go +++ b/common/http_agent/put.go @@ -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 { diff --git a/common/logger/logger.go b/common/logger/logger.go index 099e958d..00555c14 100644 --- a/common/logger/logger.go +++ b/common/logger/logger.go @@ -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) } diff --git a/common/logger/logger_test.go b/common/logger/logger_test.go index ca799cc8..0fc87152 100644 --- a/common/logger/logger_test.go +++ b/common/logger/logger_test.go @@ -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) }() diff --git a/common/monitor/monitor.go b/common/monitor/monitor.go new file mode 100644 index 00000000..e17ed59b --- /dev/null +++ b/common/monitor/monitor.go @@ -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) +} diff --git a/common/monitor/monitor_test.go b/common/monitor/monitor_test.go new file mode 100644 index 00000000..8ca8f93b --- /dev/null +++ b/common/monitor/monitor_test.go @@ -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) + }) +} diff --git a/common/nacos_error/nacos_error.go b/common/nacos_error/nacos_error.go index d09cae62..1b654a20 100644 --- a/common/nacos_error/nacos_error.go +++ b/common/nacos_error/nacos_error.go @@ -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 { diff --git a/common/nacos_server/nacos_server.go b/common/nacos_server/nacos_server.go index 1e6ee0d8..f32179bf 100644 --- a/common/nacos_server/nacos_server.go +++ b/common/nacos_server/nacos_server.go @@ -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 + } +} diff --git a/common/nacos_server/nacos_server_test.go b/common/nacos_server/nacos_server_test.go index 010d6e37..6ca6fc75 100644 --- a/common/nacos_server/nacos_server_test.go +++ b/common/nacos_server/nacos_server_test.go @@ -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" ) diff --git a/common/remote/rpc/connection.go b/common/remote/rpc/connection.go new file mode 100644 index 00000000..0d3cfd13 --- /dev/null +++ b/common/remote/rpc/connection.go @@ -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() +} diff --git a/common/remote/rpc/connection_event_listener.go b/common/remote/rpc/connection_event_listener.go new file mode 100644 index 00000000..0c867fb7 --- /dev/null +++ b/common/remote/rpc/connection_event_listener.go @@ -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() +} diff --git a/common/remote/rpc/connection_test.go b/common/remote/rpc/connection_test.go new file mode 100644 index 00000000..bd830a8c --- /dev/null +++ b/common/remote/rpc/connection_test.go @@ -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) { + +} diff --git a/common/remote/rpc/grpc_client.go b/common/remote/rpc/grpc_client.go new file mode 100644 index 00000000..3788d77c --- /dev/null +++ b/common/remote/rpc/grpc_client.go @@ -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()) + } +} diff --git a/common/remote/rpc/grpc_connection.go b/common/remote/rpc/grpc_connection.go new file mode 100644 index 00000000..f0fc61c8 --- /dev/null +++ b/common/remote/rpc/grpc_connection.go @@ -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())}, + } +} diff --git a/common/remote/rpc/rpc_client.go b/common/remote/rpc/rpc_client.go new file mode 100644 index 00000000..b374bb4d --- /dev/null +++ b/common/remote/rpc/rpc_client.go @@ -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 +} diff --git a/common/remote/rpc/rpc_client_test.go b/common/remote/rpc/rpc_client_test.go new file mode 100644 index 00000000..8920eeec --- /dev/null +++ b/common/remote/rpc/rpc_client_test.go @@ -0,0 +1,7 @@ +package rpc + +import "testing" + +func TestHealthCheck(t *testing.T) { + +} diff --git a/common/remote/rpc/rpc_request/config_request.go b/common/remote/rpc/rpc_request/config_request.go new file mode 100644 index 00000000..49343587 --- /dev/null +++ b/common/remote/rpc/rpc_request/config_request.go @@ -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" +} diff --git a/common/remote/rpc/rpc_request/internal_request.go b/common/remote/rpc/rpc_request/internal_request.go new file mode 100644 index 00000000..7f2c1bf8 --- /dev/null +++ b/common/remote/rpc/rpc_request/internal_request.go @@ -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" +} diff --git a/common/remote/rpc/rpc_request/naming_request.go b/common/remote/rpc/rpc_request/naming_request.go new file mode 100644 index 00000000..6e420127 --- /dev/null +++ b/common/remote/rpc/rpc_request/naming_request.go @@ -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" +} diff --git a/common/remote/rpc/rpc_request/rpc_request.go b/common/remote/rpc/rpc_request/rpc_request.go new file mode 100644 index 00000000..041bba11 --- /dev/null +++ b/common/remote/rpc/rpc_request/rpc_request.go @@ -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 "" +} diff --git a/common/remote/rpc/rpc_response/config_response.go b/common/remote/rpc/rpc_response/config_response.go new file mode 100644 index 00000000..de060bfa --- /dev/null +++ b/common/remote/rpc/rpc_response/config_response.go @@ -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" +} diff --git a/common/remote/rpc/rpc_response/naming_response.go b/common/remote/rpc/rpc_response/naming_response.go new file mode 100644 index 00000000..8d6fa2bb --- /dev/null +++ b/common/remote/rpc/rpc_response/naming_response.go @@ -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" +} diff --git a/common/remote/rpc/rpc_response/rpc_response.go b/common/remote/rpc/rpc_response/rpc_response.go new file mode 100644 index 00000000..ac20546a --- /dev/null +++ b/common/remote/rpc/rpc_response/rpc_response.go @@ -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" + } +} diff --git a/common/remote/rpc/server_request_handler.go b/common/remote/rpc/server_request_handler.go new file mode 100644 index 00000000..66c5c24e --- /dev/null +++ b/common/remote/rpc/server_request_handler.go @@ -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(¬ifySubscriberRequest.ServiceInfo) + return &rpc_response.NotifySubscriberResponse{ + Response: &rpc_response.Response{ResultCode: constant.RESPONSE_CODE_SUCCESS}, + } + } + return nil +} diff --git a/common/security/security_proxy.go b/common/security/security_proxy.go index b9c2ed4e..c211c4de 100644 --- a/common/security/security_proxy.go +++ b/common/security/security_proxy.go @@ -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) } diff --git a/common/tls/tls.go b/common/tls/tls.go new file mode 100644 index 00000000..da063e09 --- /dev/null +++ b/common/tls/tls.go @@ -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 +} diff --git a/common/tls/tls_test.go b/common/tls/tls_test.go new file mode 100644 index 00000000..69d4f5ba --- /dev/null +++ b/common/tls/tls_test.go @@ -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) + }) +} diff --git a/example/config-acm/main.go b/example/config-acm/main.go index 6a2143d1..30462b99 100644 --- a/example/config-acm/main.go +++ b/example/config-acm/main.go @@ -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() { diff --git a/example/config/main.go b/example/config/main.go index ebbe77c1..4a9708ae 100644 --- a/example/config/main.go +++ b/example/config/main.go @@ -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: "", diff --git a/example/service/main.go b/example/service/main.go index f1b3dc50..84606f3e 100644 --- a/example/service/main.go +++ b/example/service/main.go @@ -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", + //DeRegister + ExampleServiceClient_DeRegisterServiceInstance(client, vo.DeregisterInstanceParam{ + Ip: "10.0.0.10", Port: 8848, ServiceName: "demo.go", - Weight: 10, - ClusterName: "cluster-b", - Enable: true, - Healthy: true, - Ephemeral: true, + GroupName: "group-a", + Cluster: "cluster-a", + Ephemeral: true, //it must be true }) - //Register different group + //Register ExampleServiceClient_RegisterServiceInstance(client, vo.RegisterInstanceParam{ - Ip: "10.0.0.13", + Ip: "10.0.0.10", 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", + ClusterName: "cluster-a", Weight: 10, - ClusterName: "cluster-b", - GroupName: "group-b", Enable: true, Healthy: true, Ephemeral: true, + Metadata: map[string]string{"idc": "shanghai"}, }) - //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. - 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", - 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", - Port: 8848, - ServiceName: "demo.go", - Cluster: "cluster-b", - GroupName: "group-b", - Ephemeral: true, //it must be true - }) + time.Sleep(1 * time.Second) - //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 + //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, - }) } diff --git a/example/service/service_client_example.go b/example/service/service_client_example.go index b6ffa92c..a72d6b15 100644 --- a/example/service/service_client_example.go +++ b/example/service/service_client_example.go @@ -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) } diff --git a/go.mod b/go.mod index 2e932492..e66cd2fb 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 6ee2b040..29227412 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/mock/mock_config_client_interface.go b/mock/mock_config_client_interface.go index 3ace20e3..01e7d354 100644 --- a/mock/mock_config_client_interface.go +++ b/mock/mock_config_client_interface.go @@ -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 diff --git a/mock/mock_nacos_client_interface.go b/mock/mock_nacos_client_interface.go index 9997859d..fffc6918 100644 --- a/mock/mock_nacos_client_interface.go +++ b/mock/mock_nacos_client_interface.go @@ -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 diff --git a/mock/mock_naming_client_interface.go b/mock/mock_naming_client_interface.go index 6b710659..cb692989 100644 --- a/mock/mock_naming_client_interface.go +++ b/mock/mock_naming_client_interface.go @@ -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 diff --git a/model/config.go b/model/config.go index 5d13a14a..f4668827 100644 --- a/model/config.go +++ b/model/config.go @@ -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"` +} diff --git a/model/service.go b/model/service.go index 6e0ad4ea..abe0e31f 100644 --- a/model/service.go +++ b/model/service.go @@ -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"` diff --git a/util/common.go b/util/common.go index c234b1a1..d15fe62b 100644 --- a/util/common.go +++ b/util/common.go @@ -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,91 +66,37 @@ 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 - } - - localIP = ipNet.IP.String() - break - } - - if localIP != "" { - 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 localIP == "" { + netInterfaces, err := net.Interfaces() if err != nil { - return err + logger.Errorf("get Interfaces failed,err:%+v", err) + return "" } - 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 + 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 + } + } + } } - upAddrs = append(upAddrs, addresses...) - } - - return upAddrs, nil -} - -func isFilteredIP(ip net.IP) bool { - for _, privateIP := range privateCIDR { - if privateIP.Contains(ip) { - return true + if len(localIP) > 0 { + logger.Infof("Local IP:%s", localIP) } } - return false + return localIP } func GetDurationWithDefault(metadata map[string]string, key string, defaultDuration time.Duration) time.Duration { @@ -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 { diff --git a/util/common_test.go b/util/common_test.go deleted file mode 100644 index 83b8d544..00000000 --- a/util/common_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/util/content.go b/util/content.go new file mode 100644 index 00000000..906e2f2c --- /dev/null +++ b/util/content.go @@ -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] +} diff --git a/util/object2param.go b/util/object2param.go index e02dd3d6..de798308 100644 --- a/util/object2param.go +++ b/util/object2param.go @@ -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) { diff --git a/vo/client_param.go b/vo/client_param.go index cbe27d75..f48a5b75 100644 --- a/vo/client_param.go +++ b/vo/client_param.go @@ -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 diff --git a/vo/config_param.go b/vo/config_param.go index 77c7b8f9..018e9913 100644 --- a/vo/config_param.go +++ b/vo/config_param.go @@ -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"` diff --git a/vo/service_param.go b/vo/service_param.go index eb92017e..915d25c5 100644 --- a/vo/service_param.go +++ b/vo/service_param.go @@ -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 {