github.com/nacos-group/nacos-sdk-go@v1.1.4/clients/config_client/config_client.go (about)

     1  /*
     2   * Copyright 1999-2020 Alibaba Group Holding Ltd.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *      http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package config_client
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"math"
    23  	"net/url"
    24  	"os"
    25  	"strconv"
    26  	"strings"
    27  	"sync"
    28  	"sync/atomic"
    29  	"time"
    30  
    31  	"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
    32  
    33  	"github.com/nacos-group/nacos-sdk-go/clients/cache"
    34  	"github.com/nacos-group/nacos-sdk-go/clients/nacos_client"
    35  	"github.com/nacos-group/nacos-sdk-go/common/constant"
    36  	"github.com/nacos-group/nacos-sdk-go/common/http_agent"
    37  	"github.com/nacos-group/nacos-sdk-go/common/logger"
    38  	"github.com/nacos-group/nacos-sdk-go/common/nacos_error"
    39  	"github.com/nacos-group/nacos-sdk-go/model"
    40  	"github.com/nacos-group/nacos-sdk-go/util"
    41  	"github.com/nacos-group/nacos-sdk-go/vo"
    42  )
    43  
    44  type ConfigClient struct {
    45  	nacos_client.INacosClient
    46  	kmsClient        *kms.Client
    47  	localConfigs     []vo.ConfigParam
    48  	mutex            sync.Mutex
    49  	configProxy      ConfigProxy
    50  	configCacheDir   string
    51  	currentTaskCount int32
    52  	cacheMap         cache.ConcurrentMap
    53  	schedulerMap     cache.ConcurrentMap
    54  }
    55  
    56  const (
    57  	perTaskConfigSize = 3000
    58  	executorErrDelay  = 5 * time.Second
    59  )
    60  
    61  type cacheData struct {
    62  	isInitializing    bool
    63  	dataId            string
    64  	group             string
    65  	content           string
    66  	tenant            string
    67  	cacheDataListener *cacheDataListener
    68  	md5               string
    69  	appName           string
    70  	taskId            int
    71  }
    72  
    73  type cacheDataListener struct {
    74  	listener vo.Listener
    75  	lastMd5  string
    76  }
    77  
    78  func NewConfigClient(nc nacos_client.INacosClient) (*ConfigClient, error) {
    79  	config := &ConfigClient{
    80  		cacheMap:     cache.NewConcurrentMap(),
    81  		schedulerMap: cache.NewConcurrentMap(),
    82  	}
    83  	config.schedulerMap.Set("root", true)
    84  	go config.delayScheduler(time.NewTimer(1*time.Millisecond), 500*time.Millisecond, "root", config.listenConfigExecutor())
    85  
    86  	config.INacosClient = nc
    87  	clientConfig, err := nc.GetClientConfig()
    88  	if err != nil {
    89  		return config, err
    90  	}
    91  	serverConfig, err := nc.GetServerConfig()
    92  	if err != nil {
    93  		return config, err
    94  	}
    95  	httpAgent, err := nc.GetHttpAgent()
    96  	if err != nil {
    97  		return config, err
    98  	}
    99  	loggerConfig := logger.Config{
   100  		LogFileName:      constant.LOG_FILE_NAME,
   101  		Level:            clientConfig.LogLevel,
   102  		Sampling:         clientConfig.LogSampling,
   103  		LogRollingConfig: clientConfig.LogRollingConfig,
   104  		LogDir:           clientConfig.LogDir,
   105  		CustomLogger:     clientConfig.CustomLogger,
   106  		LogStdout:        clientConfig.AppendToStdout,
   107  	}
   108  	err = logger.InitLogger(loggerConfig)
   109  	if err != nil {
   110  		return config, err
   111  	}
   112  	logger.GetLogger().Infof("logDir:<%s>   cacheDir:<%s>", clientConfig.LogDir, clientConfig.CacheDir)
   113  	config.configCacheDir = clientConfig.CacheDir + string(os.PathSeparator) + "config"
   114  	config.configProxy, err = NewConfigProxy(serverConfig, clientConfig, httpAgent)
   115  	if clientConfig.OpenKMS {
   116  		kmsClient, err := kms.NewClientWithAccessKey(clientConfig.RegionId, clientConfig.AccessKey, clientConfig.SecretKey)
   117  		if err != nil {
   118  			return config, err
   119  		}
   120  		config.kmsClient = kmsClient
   121  	}
   122  	return config, err
   123  }
   124  
   125  func (client *ConfigClient) sync() (clientConfig constant.ClientConfig,
   126  	serverConfigs []constant.ServerConfig, agent http_agent.IHttpAgent, err error) {
   127  	clientConfig, err = client.GetClientConfig()
   128  	if err != nil {
   129  		logger.Errorf("getClientConfig catch error:%+v", err)
   130  		return
   131  	}
   132  	serverConfigs, err = client.GetServerConfig()
   133  	if err != nil {
   134  		logger.Errorf("getServerConfig catch error:%+v", err)
   135  		return
   136  	}
   137  
   138  	agent, err = client.GetHttpAgent()
   139  	if err != nil {
   140  		logger.Errorf("getHttpAgent catch error:%+v", err)
   141  	}
   142  	return
   143  }
   144  
   145  func (client *ConfigClient) GetConfig(param vo.ConfigParam) (content string, err error) {
   146  	content, err = client.getConfigInner(param)
   147  
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  
   152  	return client.decrypt(param.DataId, content)
   153  }
   154  
   155  func (client *ConfigClient) decrypt(dataId, content string) (string, error) {
   156  	if client.kmsClient != nil && strings.HasPrefix(dataId, "cipher-") {
   157  		request := kms.CreateDecryptRequest()
   158  		request.Method = "POST"
   159  		request.Scheme = "https"
   160  		request.AcceptFormat = "json"
   161  		request.CiphertextBlob = content
   162  		response, err := client.kmsClient.Decrypt(request)
   163  		if err != nil {
   164  			return "", fmt.Errorf("kms decrypt failed: %v", err)
   165  		}
   166  		content = response.Plaintext
   167  	}
   168  	return content, nil
   169  }
   170  
   171  func (client *ConfigClient) encrypt(dataId, content string) (string, error) {
   172  	if client.kmsClient != nil && strings.HasPrefix(dataId, "cipher-") {
   173  		request := kms.CreateEncryptRequest()
   174  		request.Method = "POST"
   175  		request.Scheme = "https"
   176  		request.AcceptFormat = "json"
   177  		request.KeyId = "alias/acs/acm" // use default key
   178  		request.Plaintext = content
   179  		response, err := client.kmsClient.Encrypt(request)
   180  		if err != nil {
   181  			return "", fmt.Errorf("kms encrypt failed: %v", err)
   182  		}
   183  		content = response.CiphertextBlob
   184  	}
   185  	return content, nil
   186  }
   187  
   188  func (client *ConfigClient) getConfigInner(param vo.ConfigParam) (content string, err error) {
   189  	if len(param.DataId) <= 0 {
   190  		err = errors.New("[client.GetConfig] param.dataId can not be empty")
   191  		return "", err
   192  	}
   193  	if len(param.Group) <= 0 {
   194  		err = errors.New("[client.GetConfig] param.group can not be empty")
   195  		return "", err
   196  	}
   197  	clientConfig, _ := client.GetClientConfig()
   198  	cacheKey := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
   199  	content, err = client.configProxy.GetConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
   200  
   201  	if err != nil {
   202  		logger.Errorf("get config from server error:%+v ", err)
   203  		if _, ok := err.(*nacos_error.NacosError); ok {
   204  			nacosErr := err.(*nacos_error.NacosError)
   205  			if nacosErr.ErrorCode() == "404" {
   206  				cache.WriteConfigToFile(cacheKey, client.configCacheDir, "")
   207  				logger.Warnf("[client.GetConfig] config not found, dataId: %s, group: %s, namespaceId: %s.", param.DataId, param.Group, clientConfig.NamespaceId)
   208  				return "", nil
   209  			}
   210  			if nacosErr.ErrorCode() == "403" {
   211  				return "", errors.New("get config forbidden")
   212  			}
   213  		}
   214  		content, err = cache.ReadConfigFromFile(cacheKey, client.configCacheDir)
   215  		if err != nil {
   216  			logger.Errorf("get config from cache  error:%+v ", err)
   217  			return "", errors.New("read config from both server and cache fail")
   218  		}
   219  
   220  	} else {
   221  		cache.WriteConfigToFile(cacheKey, client.configCacheDir, content)
   222  	}
   223  	return content, nil
   224  }
   225  
   226  func (client *ConfigClient) PublishConfig(param vo.ConfigParam) (published bool,
   227  	err error) {
   228  	if len(param.DataId) <= 0 {
   229  		err = errors.New("[client.PublishConfig] param.dataId can not be empty")
   230  	}
   231  	if len(param.Group) <= 0 {
   232  		err = errors.New("[client.PublishConfig] param.group can not be empty")
   233  	}
   234  	if len(param.Content) <= 0 {
   235  		err = errors.New("[client.PublishConfig] param.content can not be empty")
   236  	}
   237  
   238  	param.Content, err = client.encrypt(param.DataId, param.Content)
   239  	if err != nil {
   240  		return false, err
   241  	}
   242  	clientConfig, _ := client.GetClientConfig()
   243  	return client.configProxy.PublishConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
   244  }
   245  
   246  func (client *ConfigClient) DeleteConfig(param vo.ConfigParam) (deleted bool, err error) {
   247  	if len(param.DataId) <= 0 {
   248  		err = errors.New("[client.DeleteConfig] param.dataId can not be empty")
   249  	}
   250  	if len(param.Group) <= 0 {
   251  		err = errors.New("[client.DeleteConfig] param.group can not be empty")
   252  	}
   253  
   254  	clientConfig, _ := client.GetClientConfig()
   255  	return client.configProxy.DeleteConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
   256  }
   257  
   258  // Cancel Listen Config
   259  func (client *ConfigClient) CancelListenConfig(param vo.ConfigParam) (err error) {
   260  	clientConfig, err := client.GetClientConfig()
   261  	if err != nil {
   262  		logger.Errorf("[checkConfigInfo.GetClientConfig] failed,err:%+v", err)
   263  		return
   264  	}
   265  	client.cacheMap.Remove(util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId))
   266  	logger.Infof("Cancel listen config DataId:%s Group:%s", param.DataId, param.Group)
   267  	remakeId := int(math.Ceil(float64(client.cacheMap.Count()) / float64(perTaskConfigSize)))
   268  	currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))
   269  	if remakeId < currentTaskCount {
   270  		client.remakeCacheDataTaskId(remakeId)
   271  	}
   272  	return err
   273  }
   274  
   275  // Remake cache data taskId
   276  func (client *ConfigClient) remakeCacheDataTaskId(remakeId int) {
   277  	for i := 0; i < remakeId; i++ {
   278  		count := 0
   279  		for _, key := range client.cacheMap.Keys() {
   280  			if count == perTaskConfigSize {
   281  				break
   282  			}
   283  			if value, ok := client.cacheMap.Get(key); ok {
   284  				cData := value.(cacheData)
   285  				cData.taskId = i
   286  				client.cacheMap.Set(key, cData)
   287  			}
   288  			count++
   289  		}
   290  	}
   291  }
   292  
   293  func (client *ConfigClient) ListenConfig(param vo.ConfigParam) (err error) {
   294  	if len(param.DataId) <= 0 {
   295  		err = errors.New("[client.ListenConfig] DataId can not be empty")
   296  		return err
   297  	}
   298  	if len(param.Group) <= 0 {
   299  		err = errors.New("[client.ListenConfig] Group can not be empty")
   300  		return err
   301  	}
   302  	clientConfig, err := client.GetClientConfig()
   303  	if err != nil {
   304  		err = errors.New("[checkConfigInfo.GetClientConfig] failed")
   305  		return err
   306  	}
   307  
   308  	key := util.GetConfigCacheKey(param.DataId, param.Group, clientConfig.NamespaceId)
   309  	var cData cacheData
   310  	if v, ok := client.cacheMap.Get(key); ok {
   311  		cData = v.(cacheData)
   312  		cData.isInitializing = true
   313  	} else {
   314  		var (
   315  			content string
   316  			md5Str  string
   317  		)
   318  		if content, _ = cache.ReadConfigFromFile(key, client.configCacheDir); len(content) > 0 {
   319  			md5Str = util.Md5(content)
   320  		}
   321  		listener := &cacheDataListener{
   322  			listener: param.OnChange,
   323  			lastMd5:  md5Str,
   324  		}
   325  
   326  		cData = cacheData{
   327  			isInitializing:    true,
   328  			dataId:            param.DataId,
   329  			group:             param.Group,
   330  			tenant:            clientConfig.NamespaceId,
   331  			content:           content,
   332  			md5:               md5Str,
   333  			cacheDataListener: listener,
   334  			taskId:            client.cacheMap.Count() / perTaskConfigSize,
   335  		}
   336  	}
   337  	client.cacheMap.Set(key, cData)
   338  	return
   339  }
   340  
   341  // Delay Scheduler
   342  // initialDelay the time to delay first execution
   343  // delay the delay between the termination of one execution and the commencement of the next
   344  func (client *ConfigClient) delayScheduler(t *time.Timer, delay time.Duration, taskId string, execute func() error) {
   345  	for {
   346  		if v, ok := client.schedulerMap.Get(taskId); ok {
   347  			if !v.(bool) {
   348  				return
   349  			}
   350  		}
   351  		<-t.C
   352  		d := delay
   353  		if err := execute(); err != nil {
   354  			d = executorErrDelay
   355  		}
   356  		t.Reset(d)
   357  	}
   358  }
   359  
   360  // Listen for the configuration executor
   361  func (client *ConfigClient) listenConfigExecutor() func() error {
   362  	return func() error {
   363  		listenerSize := client.cacheMap.Count()
   364  		taskCount := int(math.Ceil(float64(listenerSize) / float64(perTaskConfigSize)))
   365  		currentTaskCount := int(atomic.LoadInt32(&client.currentTaskCount))
   366  		if taskCount > currentTaskCount {
   367  			for i := currentTaskCount; i < taskCount; i++ {
   368  				client.schedulerMap.Set(strconv.Itoa(i), true)
   369  				go client.delayScheduler(time.NewTimer(1*time.Millisecond), 10*time.Millisecond, strconv.Itoa(i), client.longPulling(i))
   370  			}
   371  			atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
   372  		} else if taskCount < currentTaskCount {
   373  			for i := taskCount; i < currentTaskCount; i++ {
   374  				if _, ok := client.schedulerMap.Get(strconv.Itoa(i)); ok {
   375  					client.schedulerMap.Set(strconv.Itoa(i), false)
   376  				}
   377  			}
   378  			atomic.StoreInt32(&client.currentTaskCount, int32(taskCount))
   379  		}
   380  		return nil
   381  	}
   382  }
   383  
   384  // Long polling listening configuration
   385  func (client *ConfigClient) longPulling(taskId int) func() error {
   386  	return func() error {
   387  		var listeningConfigs string
   388  		initializationList := make([]cacheData, 0)
   389  		for _, key := range client.cacheMap.Keys() {
   390  			if value, ok := client.cacheMap.Get(key); ok {
   391  				cData := value.(cacheData)
   392  				if cData.taskId == taskId {
   393  					if cData.isInitializing {
   394  						initializationList = append(initializationList, cData)
   395  					}
   396  					if len(cData.tenant) > 0 {
   397  						listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
   398  							cData.md5 + constant.SPLIT_CONFIG_INNER + cData.tenant + constant.SPLIT_CONFIG
   399  					} else {
   400  						listeningConfigs += cData.dataId + constant.SPLIT_CONFIG_INNER + cData.group + constant.SPLIT_CONFIG_INNER +
   401  							cData.md5 + constant.SPLIT_CONFIG
   402  					}
   403  				}
   404  			}
   405  		}
   406  		if len(listeningConfigs) > 0 {
   407  			clientConfig, err := client.GetClientConfig()
   408  			if err != nil {
   409  				logger.Errorf("[checkConfigInfo.GetClientConfig] err: %+v", err)
   410  				return err
   411  			}
   412  			// http get
   413  			params := make(map[string]string)
   414  			params[constant.KEY_LISTEN_CONFIGS] = listeningConfigs
   415  
   416  			var changed string
   417  			changedTmp, err := client.configProxy.ListenConfig(params, len(initializationList) > 0, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
   418  			if err == nil {
   419  				changed = changedTmp
   420  			} else {
   421  				if _, ok := err.(*nacos_error.NacosError); ok {
   422  					changed = changedTmp
   423  				} else {
   424  					logger.Errorf("[client.ListenConfig] listen config error: %+v", err)
   425  				}
   426  				return err
   427  			}
   428  			for _, v := range initializationList {
   429  				v.isInitializing = false
   430  				client.cacheMap.Set(util.GetConfigCacheKey(v.dataId, v.group, v.tenant), v)
   431  			}
   432  			if len(strings.ToLower(strings.Trim(changed, " "))) == 0 {
   433  				logger.Info("[client.ListenConfig] no change")
   434  			} else {
   435  				logger.Info("[client.ListenConfig] config changed:" + changed)
   436  				client.callListener(changed, clientConfig.NamespaceId)
   437  			}
   438  		}
   439  		return nil
   440  	}
   441  
   442  }
   443  
   444  // Execute the Listener callback func()
   445  func (client *ConfigClient) callListener(changed, tenant string) {
   446  	changedDecoded, _ := url.QueryUnescape(changed)
   447  	changedConfigs := strings.Split(changedDecoded, "\u0001")
   448  	for _, config := range changedConfigs {
   449  		attrs := strings.Split(config, "\u0002")
   450  		if len(attrs) >= 2 {
   451  			if value, ok := client.cacheMap.Get(util.GetConfigCacheKey(attrs[0], attrs[1], tenant)); ok {
   452  				cData := value.(cacheData)
   453  				content, err := client.getConfigInner(vo.ConfigParam{
   454  					DataId: cData.dataId,
   455  					Group:  cData.group,
   456  				})
   457  				if err != nil {
   458  					logger.Errorf("[client.getConfigInner] DataId:[%s] Group:[%s] Error:[%+v]", cData.dataId, cData.group, err)
   459  					continue
   460  				}
   461  				cData.content = content
   462  				cData.md5 = util.Md5(content)
   463  				if cData.md5 != cData.cacheDataListener.lastMd5 {
   464  					go cData.cacheDataListener.listener(tenant, attrs[1], attrs[0], cData.content)
   465  					cData.cacheDataListener.lastMd5 = cData.md5
   466  					client.cacheMap.Set(util.GetConfigCacheKey(cData.dataId, cData.group, tenant), cData)
   467  				}
   468  			}
   469  		}
   470  	}
   471  }
   472  
   473  func (client *ConfigClient) buildBasePath(serverConfig constant.ServerConfig) (basePath string) {
   474  	basePath = "http://" + serverConfig.IpAddr + ":" +
   475  		strconv.FormatUint(serverConfig.Port, 10) + serverConfig.ContextPath + constant.CONFIG_PATH
   476  	return
   477  }
   478  
   479  func (client *ConfigClient) SearchConfig(param vo.SearchConfigParam) (*model.ConfigPage, error) {
   480  	return client.searchConfigInner(param)
   481  }
   482  
   483  func (client *ConfigClient) PublishAggr(param vo.ConfigParam) (published bool,
   484  	err error) {
   485  	if len(param.DataId) <= 0 {
   486  		err = errors.New("[client.PublishAggr] param.dataId can not be empty")
   487  	}
   488  	if len(param.Group) <= 0 {
   489  		err = errors.New("[client.PublishAggr] param.group can not be empty")
   490  	}
   491  	if len(param.Content) <= 0 {
   492  		err = errors.New("[client.PublishAggr] param.content can not be empty")
   493  	}
   494  	if len(param.DatumId) <= 0 {
   495  		err = errors.New("[client.PublishAggr] param.DatumId can not be empty")
   496  	}
   497  	clientConfig, _ := client.GetClientConfig()
   498  	return client.configProxy.PublishAggProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
   499  }
   500  
   501  func (client *ConfigClient) RemoveAggr(param vo.ConfigParam) (published bool,
   502  	err error) {
   503  	if len(param.DataId) <= 0 {
   504  		err = errors.New("[client.DeleteAggr] param.dataId can not be empty")
   505  	}
   506  	if len(param.Group) <= 0 {
   507  		err = errors.New("[client.DeleteAggr] param.group can not be empty")
   508  	}
   509  	if len(param.Content) <= 0 {
   510  		err = errors.New("[client.DeleteAggr] param.content can not be empty")
   511  	}
   512  	if len(param.DatumId) <= 0 {
   513  		err = errors.New("[client.DeleteAggr] param.DatumId can not be empty")
   514  	}
   515  	clientConfig, _ := client.GetClientConfig()
   516  	return client.configProxy.DeleteAggProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
   517  }
   518  
   519  func (client *ConfigClient) searchConfigInner(param vo.SearchConfigParam) (*model.ConfigPage, error) {
   520  	if param.Search != "accurate" && param.Search != "blur" {
   521  		return nil, errors.New("[client.searchConfigInner] param.search must be accurate or blur")
   522  	}
   523  	if param.PageNo <= 0 {
   524  		param.PageNo = 1
   525  	}
   526  	if param.PageSize <= 0 {
   527  		param.PageSize = 10
   528  	}
   529  	clientConfig, _ := client.GetClientConfig()
   530  	configItems, err := client.configProxy.SearchConfigProxy(param, clientConfig.NamespaceId, clientConfig.AccessKey, clientConfig.SecretKey)
   531  	if err != nil {
   532  		logger.Errorf("search config from server error:%+v ", err)
   533  		if _, ok := err.(*nacos_error.NacosError); ok {
   534  			nacosErr := err.(*nacos_error.NacosError)
   535  			if nacosErr.ErrorCode() == "404" {
   536  				return nil, nil
   537  			}
   538  			if nacosErr.ErrorCode() == "403" {
   539  				return nil, errors.New("get config forbidden")
   540  			}
   541  		}
   542  		return nil, err
   543  	}
   544  	return configItems, nil
   545  }