github.com/polarismesh/polaris@v1.17.8/config/client.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package config
    19  
    20  import (
    21  	"context"
    22  	"encoding/base64"
    23  
    24  	apiconfig "github.com/polarismesh/specification/source/go/api/v1/config_manage"
    25  	apimodel "github.com/polarismesh/specification/source/go/api/v1/model"
    26  	"go.uber.org/zap"
    27  
    28  	api "github.com/polarismesh/polaris/common/api/v1"
    29  	"github.com/polarismesh/polaris/common/model"
    30  	"github.com/polarismesh/polaris/common/rsa"
    31  	commontime "github.com/polarismesh/polaris/common/time"
    32  	"github.com/polarismesh/polaris/common/utils"
    33  )
    34  
    35  type (
    36  	compareFunction func(clientInfo *apiconfig.ClientConfigFileInfo, file *model.ConfigFileRelease) bool
    37  )
    38  
    39  // GetConfigFileForClient 从缓存中获取配置文件,如果客户端的版本号大于服务端,则服务端重新加载缓存
    40  func (s *Server) GetConfigFileForClient(ctx context.Context,
    41  	client *apiconfig.ClientConfigFileInfo) *apiconfig.ConfigClientResponse {
    42  	namespace := client.GetNamespace().GetValue()
    43  	group := client.GetGroup().GetValue()
    44  	fileName := client.GetFileName().GetValue()
    45  	clientVersion := client.GetVersion().GetValue()
    46  
    47  	if namespace == "" || group == "" || fileName == "" {
    48  		return api.NewConfigClientResponseWithInfo(
    49  			apimodel.Code_BadRequest, "namespace & group & fileName can not be empty")
    50  	}
    51  	// 从缓存中获取配置内容
    52  	release := s.fileCache.GetActiveRelease(namespace, group, fileName)
    53  	if release == nil {
    54  		return api.NewConfigClientResponse(apimodel.Code_NotFoundResource, nil)
    55  	}
    56  
    57  	// 客户端版本号大于服务端版本号,服务端不返回变更
    58  	if clientVersion > release.Version {
    59  		return api.NewConfigClientResponse(apimodel.Code_DataNoChange, nil)
    60  	}
    61  	configFile, err := toClientInfo(client, release)
    62  	if err != nil {
    63  		log.Error("[Config][Service] get config file to client info", utils.RequestID(ctx), zap.Error(err))
    64  		return api.NewConfigClientResponseWithInfo(apimodel.Code_ExecuteException, err.Error())
    65  	}
    66  	log.Info("[Config][Client] client get config file success.", utils.RequestID(ctx),
    67  		zap.String("client", utils.ParseClientAddress(ctx)), zap.String("file", fileName),
    68  		zap.Uint64("version", release.Version))
    69  	return api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, configFile)
    70  }
    71  
    72  // CreateConfigFileFromClient 调用config_file接口获取配置文件
    73  func (s *Server) CreateConfigFileFromClient(ctx context.Context,
    74  	client *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse {
    75  	configResponse := s.CreateConfigFile(ctx, client)
    76  	return api.NewConfigClientResponseFromConfigResponse(configResponse)
    77  }
    78  
    79  // UpdateConfigFileFromClient 调用config_file接口更新配置文件
    80  func (s *Server) UpdateConfigFileFromClient(ctx context.Context,
    81  	client *apiconfig.ConfigFile) *apiconfig.ConfigClientResponse {
    82  	configResponse := s.UpdateConfigFile(ctx, client)
    83  	return api.NewConfigClientResponseFromConfigResponse(configResponse)
    84  }
    85  
    86  // PublishConfigFileFromClient 调用config_file_release接口发布配置文件
    87  func (s *Server) PublishConfigFileFromClient(ctx context.Context,
    88  	client *apiconfig.ConfigFileRelease) *apiconfig.ConfigClientResponse {
    89  	configResponse := s.PublishConfigFile(ctx, client)
    90  	return api.NewConfigClientResponseFromConfigResponse(configResponse)
    91  }
    92  
    93  func (s *Server) WatchConfigFiles(ctx context.Context,
    94  	req *apiconfig.ClientWatchConfigFileRequest) (WatchCallback, error) {
    95  
    96  	watchFiles := req.GetWatchFiles()
    97  	// 2. 检查客户端是否有版本落后
    98  	resp, needWatch := s.checkClientConfigFile(ctx, watchFiles, compareByVersion)
    99  	if !needWatch {
   100  		return func() *apiconfig.ConfigClientResponse {
   101  			return resp
   102  		}, nil
   103  	}
   104  
   105  	// 3. 监听配置变更,hold 请求 30s,30s 内如果有配置发布,则响应请求
   106  	clientId := utils.ParseClientAddress(ctx) + "@" + utils.NewUUID()[0:8]
   107  	finishChan := s.ConnManager().AddConn(clientId, watchFiles)
   108  	return func() *apiconfig.ConfigClientResponse {
   109  		return <-finishChan
   110  	}, nil
   111  }
   112  
   113  // GetConfigFileNamesWithCache
   114  func (s *Server) GetConfigFileNamesWithCache(ctx context.Context,
   115  	req *apiconfig.ConfigFileGroupRequest) *apiconfig.ConfigClientListResponse {
   116  
   117  	namespace := req.GetConfigFileGroup().GetNamespace().GetValue()
   118  	group := req.GetConfigFileGroup().GetName().GetValue()
   119  
   120  	out := api.NewConfigClientListResponse(apimodel.Code_ExecuteSuccess)
   121  
   122  	if namespace == "" || group == "" {
   123  		out.Code = utils.NewUInt32Value(uint32(apimodel.Code_BadRequest))
   124  		out.Info = utils.NewStringValue("invalid namespace or group")
   125  		return out
   126  	}
   127  
   128  	releases, revision := s.fileCache.GetGroupActiveReleases(namespace, group)
   129  	if revision == req.GetRevision().GetValue() {
   130  		out.Code = utils.NewUInt32Value(uint32(apimodel.Code_DataNoChange))
   131  		return out
   132  	}
   133  	ret := make([]*apiconfig.ClientConfigFileInfo, 0, len(releases))
   134  	for i := range releases {
   135  		ret = append(ret, &apiconfig.ClientConfigFileInfo{
   136  			Namespace:   utils.NewStringValue(releases[i].Namespace),
   137  			Group:       utils.NewStringValue(releases[i].Group),
   138  			FileName:    utils.NewStringValue(releases[i].FileName),
   139  			Name:        utils.NewStringValue(releases[i].Name),
   140  			Version:     utils.NewUInt64Value(releases[i].Version),
   141  			ReleaseTime: utils.NewStringValue(commontime.Time2String(releases[i].ModifyTime)),
   142  		})
   143  	}
   144  
   145  	return &apiconfig.ConfigClientListResponse{
   146  		Code:            utils.NewUInt32Value(uint32(apimodel.Code_ExecuteSuccess)),
   147  		Info:            utils.NewStringValue(api.Code2Info(uint32(apimodel.Code_ExecuteSuccess))),
   148  		Revision:        utils.NewStringValue(revision),
   149  		Namespace:       namespace,
   150  		Group:           group,
   151  		ConfigFileInfos: ret,
   152  	}
   153  }
   154  
   155  func compareByVersion(clientInfo *apiconfig.ClientConfigFileInfo, file *model.ConfigFileRelease) bool {
   156  	return clientInfo.GetVersion().GetValue() < file.Version
   157  }
   158  
   159  func compareByMD5(clientInfo *apiconfig.ClientConfigFileInfo, file *model.ConfigFileRelease) bool {
   160  	return clientInfo.Md5.GetValue() != file.Md5
   161  }
   162  
   163  func (s *Server) checkClientConfigFile(ctx context.Context, files []*apiconfig.ClientConfigFileInfo,
   164  	compartor compareFunction) (*apiconfig.ConfigClientResponse, bool) {
   165  	if len(files) == 0 {
   166  		return api.NewConfigClientResponse(apimodel.Code_InvalidWatchConfigFileFormat, nil), false
   167  	}
   168  	for _, configFile := range files {
   169  		namespace := configFile.GetNamespace().GetValue()
   170  		group := configFile.GetGroup().GetValue()
   171  		fileName := configFile.GetFileName().GetValue()
   172  
   173  		if namespace == "" || group == "" || fileName == "" {
   174  			return api.NewConfigClientResponseWithInfo(apimodel.Code_BadRequest,
   175  				"namespace & group & fileName can not be empty"), false
   176  		}
   177  		// 从缓存中获取最新的配置文件信息
   178  		release := s.fileCache.GetActiveRelease(namespace, group, fileName)
   179  		if release != nil && compartor(configFile, release) {
   180  			ret := &apiconfig.ClientConfigFileInfo{
   181  				Namespace: utils.NewStringValue(namespace),
   182  				Group:     utils.NewStringValue(group),
   183  				FileName:  utils.NewStringValue(fileName),
   184  				Version:   utils.NewUInt64Value(release.Version),
   185  				Md5:       utils.NewStringValue(release.Md5),
   186  			}
   187  			return api.NewConfigClientResponse(apimodel.Code_ExecuteSuccess, ret), false
   188  		}
   189  	}
   190  	return api.NewConfigClientResponse(apimodel.Code_DataNoChange, nil), true
   191  }
   192  
   193  func toClientInfo(client *apiconfig.ClientConfigFileInfo,
   194  	release *model.ConfigFileRelease) (*apiconfig.ClientConfigFileInfo, error) {
   195  
   196  	namespace := client.GetNamespace().GetValue()
   197  	group := client.GetGroup().GetValue()
   198  	fileName := client.GetFileName().GetValue()
   199  	publicKey := client.GetPublicKey().GetValue()
   200  
   201  	copyMetadata := func() map[string]string {
   202  		ret := map[string]string{}
   203  		for k, v := range release.Metadata {
   204  			ret[k] = v
   205  		}
   206  		delete(ret, utils.ConfigFileTagKeyDataKey)
   207  		return ret
   208  	}()
   209  
   210  	configFile := &apiconfig.ClientConfigFileInfo{
   211  		Namespace: utils.NewStringValue(namespace),
   212  		Group:     utils.NewStringValue(group),
   213  		FileName:  utils.NewStringValue(fileName),
   214  		Content:   utils.NewStringValue(release.Content),
   215  		Version:   utils.NewUInt64Value(release.Version),
   216  		Md5:       utils.NewStringValue(release.Md5),
   217  		Encrypted: utils.NewBoolValue(release.IsEncrypted()),
   218  		Tags:      model.FromTagMap(copyMetadata),
   219  	}
   220  
   221  	dataKey := release.GetEncryptDataKey()
   222  	encryptAlgo := release.GetEncryptAlgo()
   223  	if dataKey != "" && encryptAlgo != "" {
   224  		dataKeyBytes, err := base64.StdEncoding.DecodeString(dataKey)
   225  		if err != nil {
   226  			log.Error("[Config][Service] decode data key error.", zap.String("dataKey", dataKey), zap.Error(err))
   227  			return nil, err
   228  		}
   229  		if publicKey != "" {
   230  			cipherDataKey, err := rsa.EncryptToBase64(dataKeyBytes, publicKey)
   231  			if err != nil {
   232  				log.Error("[Config][Service] rsa encrypt data key error.",
   233  					zap.String("dataKey", dataKey), zap.Error(err))
   234  			} else {
   235  				dataKey = cipherDataKey
   236  			}
   237  		}
   238  		configFile.Tags = append(configFile.Tags,
   239  			&apiconfig.ConfigFileTag{
   240  				Key:   utils.NewStringValue(utils.ConfigFileTagKeyDataKey),
   241  				Value: utils.NewStringValue(dataKey),
   242  			},
   243  		)
   244  	}
   245  	return configFile, nil
   246  }