Repository: nacos-group/nacos-sdk-go (go)

Issue:
Add function UpdateInstance()
Instance had RegisterInstance() and DeregisterInstance() currently. 
Function UpdateInstance() is useful for updating workload information, so client could choose the right instance.

Pull request description:
Add function UpdateInstance() #286
close #286 

Patch:
diff --git a/clients/config_client/config_client.go b/clients/config_client/config_client.go
index af3df8e..6192dd1 100644
--- a/clients/config_client/config_client.go
+++ b/clients/config_client/config_client.go
@@ -94,12 +94,20 @@ func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
 	if err != nil {
 		return config, err
 	}
-	err = logger.InitLogger(logger.Config{
+	loggerConfig := logger.Config{
 		Level:        clientConfig.LogLevel,
 		OutputPath:   clientConfig.LogDir,
 		RotationTime: clientConfig.RotateTime,
 		MaxAge:       clientConfig.MaxAge,
-	})
+	}
+	if clientConfig.LogSampling != nil {
+		loggerConfig.Sampling = &logger.SamplingConfig{
+			Initial:    clientConfig.LogSampling.Initial,
+			Thereafter: clientConfig.LogSampling.Thereafter,
+			Tick:       clientConfig.LogSampling.Tick,
+		}
+	}
+	err = logger.InitLogger(loggerConfig)
 	if err != nil {
 		return config, err
 	}
diff --git a/clients/nacos_client/nacos_client.go b/clients/nacos_client/nacos_client.go
index 5729266..aa1f7fb 100644
--- a/clients/nacos_client/nacos_client.go
+++ b/clients/nacos_client/nacos_client.go
@@ -20,6 +20,7 @@ import (
 	"errors"
 	"os"
 	"strconv"
+	"time"
 
 	"github.com/nacos-group/nacos-sdk-go/common/constant"
 	"github.com/nacos-group/nacos-sdk-go/common/file"
@@ -69,6 +70,20 @@ func (client *NacosClient) SetClientConfig(config constant.ClientConfig) (err er
 		config.LogDir = file.GetCurrentPath() + string(os.PathSeparator) + "log"
 	}
 
+	if config.LogSampling != nil {
+		if config.LogSampling.Initial < 0 {
+			config.LogSampling.Initial = 100
+		}
+
+		if config.LogSampling.Thereafter < 0 {
+			config.LogSampling.Thereafter = 100
+		}
+
+		if config.LogSampling.Tick < 0 {
+			config.LogSampling.Tick = 10 * time.Second
+		}
+	}
+
 	logger.Infof("logDir:<%s>   cacheDir:<%s>", config.LogDir, config.CacheDir)
 	client.clientConfig = config
 	client.clientConfigValid = true
diff --git a/clients/naming_client/naming_client.go b/clients/naming_client/naming_client.go
index fcbb957..e5235fc 100644
--- a/clients/naming_client/naming_client.go
+++ b/clients/naming_client/naming_client.go
@@ -24,6 +24,8 @@ import (
 	"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"
@@ -31,7 +33,6 @@ import (
 	"github.com/nacos-group/nacos-sdk-go/model"
 	"github.com/nacos-group/nacos-sdk-go/util"
 	"github.com/nacos-group/nacos-sdk-go/vo"
-	"github.com/pkg/errors"
 )
 
 type NamingClient struct {
@@ -66,12 +67,20 @@ func NewNamingClient(nc nacos_client.INacosClient) (NamingClient, error) {
 	if err != nil {
 		return naming, err
 	}
-	err = logger.InitLogger(logger.Config{
+	loggerConfig := logger.Config{
 		Level:        clientConfig.LogLevel,
 		OutputPath:   clientConfig.LogDir,
 		RotationTime: clientConfig.RotateTime,
 		MaxAge:       clientConfig.MaxAge,
-	})
+	}
+	if clientConfig.LogSampling != nil {
+		loggerConfig.Sampling = &logger.SamplingConfig{
+			Initial:    clientConfig.LogSampling.Initial,
+			Thereafter: clientConfig.LogSampling.Thereafter,
+			Tick:       clientConfig.LogSampling.Tick,
+		}
+	}
+	err = logger.InitLogger(loggerConfig)
 	if err != nil {
 		return naming, err
 	}
@@ -144,6 +153,40 @@ func (sc *NamingClient) DeregisterInstance(param vo.DeregisterInstanceParam) (bo
 	return true, nil
 }
 
+// UpdateInstance Update information for exist instance.
+func (sc *NamingClient) UpdateInstance(param vo.UpdateInstanceParam) (bool, error) {
+	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)
+	}
+
+	// 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)
+
+	if err != nil {
+		return false, err
+	}
+	return true, nil
+}
+
 // 获取服务列表
 func (sc *NamingClient) GetService(param vo.GetServiceParam) (model.Service, error) {
 	if len(param.GroupName) == 0 {
@@ -244,7 +287,7 @@ func random(instances []model.Instance, mw int) []model.Instance {
 	if len(instances) <= 1 || mw <= 1 {
 		return instances
 	}
-	//实例交叉插入列表，避免列表中是连续的实例
+	// 实例交叉插入列表，避免列表中是连续的实例
 	var result = make([]model.Instance, 0)
 	for i := 1; i <= mw; i++ {
 		for _, host := range instances {
@@ -310,7 +353,7 @@ func (sc *NamingClient) Subscribe(param *vo.SubscribeParam) error {
 	return nil
 }
 
-//取消服务监听
+// 取消服务监听
 func (sc *NamingClient) Unsubscribe(param *vo.SubscribeParam) error {
 	sc.subCallback.RemoveCallbackFuncs(util.GetGroupName(param.ServiceName, param.GroupName), strings.Join(param.Clusters, ","), &param.SubscribeCallback)
 	return nil
diff --git a/clients/naming_client/naming_client_interface.go b/clients/naming_client/naming_client_interface.go
index 45ebf12..e1175a4 100644
--- a/clients/naming_client/naming_client_interface.go
+++ b/clients/naming_client/naming_client_interface.go
@@ -48,6 +48,19 @@ type INamingClient interface {
 	//Ephemeral optional
 	DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error)
 
+	// UpdateInstance use to modify instance
+	// Ip required
+	// Port required
+	// Tenant optional
+	// Cluster optional,default:DEFAULT
+	// ServiceName  require
+	// GroupName  optional,default:DEFAULT_GROUP
+	// Ephemeral optional
+	// Weight  require,it must be lager than 0
+	// Enable  require,the instance can be access or not
+	// Metadata  optional
+	UpdateInstance(param vo.UpdateInstanceParam) (bool, error)
+
 	//GetService use to get service
 	//ServiceName require
 	//Clusters optional,default:DEFAULT
diff --git a/clients/naming_client/naming_proxy.go b/clients/naming_client/naming_proxy.go
index 057cd84..41c0d7e 100644
--- a/clients/naming_client/naming_proxy.go
+++ b/clients/naming_client/naming_proxy.go
@@ -23,6 +23,7 @@ import (
 	"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"
@@ -81,6 +82,22 @@ func (proxy *NamingProxy) DeregisterInstance(serviceName string, ip string, port
 	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))
diff --git a/common/constant/client_config_options.go b/common/constant/client_config_options.go
index fbf8e7c..81ba559 100644
--- a/common/constant/client_config_options.go
+++ b/common/constant/client_config_options.go
@@ -18,6 +18,7 @@ package constant
 
 import (
 	"os"
+	"time"
 
 	"github.com/nacos-group/nacos-sdk-go/common/file"
 )
@@ -172,3 +173,10 @@ func WithLogLevel(logLevel string) ClientOption {
 		config.LogLevel = logLevel
 	}
 }
+
+// WithLogSampling ...
+func WithLogSampling(tick time.Duration, initial int, thereafter int) ClientOption {
+	return func(config *ClientConfig) {
+		config.LogSampling = &ClientLogSamplingConfig{initial, thereafter, tick}
+	}
+}
diff --git a/common/constant/config.go b/common/constant/config.go
index 2b27073..55fc20e 100644
--- a/common/constant/config.go
+++ b/common/constant/config.go
@@ -16,6 +16,8 @@
 
 package constant
 
+import "time"
+
 type ServerConfig struct {
 	Scheme      string //the nacos server scheme
 	ContextPath string //the nacos server contextpath
@@ -24,25 +26,32 @@ type ServerConfig struct {
 }
 
 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
-	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
-	ContextPath          string //the nacos server contextpath
+	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
+	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
+	LogSampling          *ClientLogSamplingConfig //the sampling config of log
+	ContextPath          string                   //the nacos server contextpath
+}
+
+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
 }
diff --git a/common/logger/logger.go b/common/logger/logger.go
index 3df7462..5cab888 100644
--- a/common/logger/logger.go
+++ b/common/logger/logger.go
@@ -46,6 +46,13 @@ type Config struct {
 	OutputPath   string
 	RotationTime string
 	MaxAge       int64
+	Sampling     *SamplingConfig
+}
+
+type SamplingConfig struct {
+	Initial    int
+	Thereafter int
+	Tick       time.Duration
 }
 
 type NacosLogger struct {
@@ -101,6 +108,9 @@ func InitNacosLogger(config Config) (Logger, error) {
 		return nil, err
 	}
 	core := zapcore.NewCore(zapcore.NewConsoleEncoder(encoder), zapcore.AddSync(writer), logLevel)
+	if config.Sampling != nil {
+		core = zapcore.NewSamplerWithOptions(core, config.Sampling.Tick, config.Sampling.Initial, config.Sampling.Thereafter)
+	}
 	zaplogger := zap.New(core, zap.AddCallerSkip(1))
 	return &NacosLogger{zaplogger.Sugar()}, nil
 }
diff --git a/go.sum b/go.sum
index 8135b1b..588105a 100644
--- a/go.sum
+++ b/go.sum
@@ -76,7 +76,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
 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-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
@@ -93,7 +92,6 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
 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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 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=
diff --git a/vo/service_param.go b/vo/service_param.go
index 5120392..eb92017 100644
--- a/vo/service_param.go
+++ b/vo/service_param.go
@@ -40,6 +40,18 @@ type DeregisterInstanceParam struct {
 	Ephemeral   bool   `param:"ephemeral"`   //optional
 }
 
+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
+}
+
 type GetServiceParam struct {
 	Clusters    []string `param:"clusters"`    //optional,default:DEFAULT
 	ServiceName string   `param:"serviceName"` //required


Test patch:
diff --git a/clients/naming_client/naming_client_test.go b/clients/naming_client/naming_client_test.go
index 7a6a805..2c5c1ae 100644
--- a/clients/naming_client/naming_client_test.go
+++ b/clients/naming_client/naming_client_test.go
@@ -22,6 +22,8 @@ import (
 	"testing"
 
 	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/assert"
+
 	"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"
@@ -29,7 +31,6 @@ import (
 	"github.com/nacos-group/nacos-sdk-go/model"
 	"github.com/nacos-group/nacos-sdk-go/util"
 	"github.com/nacos-group/nacos-sdk-go/vo"
-	"github.com/stretchr/testify/assert"
 )
 
 var clientConfigTest = *constant.NewClientConfig(
@@ -286,6 +287,44 @@ func TestNamingProxy_DeregisterService_WithGroupName(t *testing.T) {
 	})
 }
 
+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() {
diff --git a/common/constant/client_config_options_test.go b/common/constant/client_config_options_test.go
index 0005789..1cf2aa9 100644
--- a/common/constant/client_config_options_test.go
+++ b/common/constant/client_config_options_test.go
@@ -19,6 +19,7 @@ package constant
 import (
 	"os"
 	"testing"
+	"time"
 
 	"github.com/nacos-group/nacos-sdk-go/common/file"
 
@@ -50,6 +51,7 @@ 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) {
@@ -75,6 +77,8 @@ 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))
@@ -98,4 +102,8 @@ 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)
 }