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 }