github.com/polarismesh/polaris@v1.17.8/store/mysql/config_file.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 sqldb
    19  
    20  import (
    21  	"database/sql"
    22  	"strings"
    23  	"time"
    24  
    25  	"go.uber.org/zap"
    26  
    27  	"github.com/polarismesh/polaris/common/model"
    28  	"github.com/polarismesh/polaris/common/utils"
    29  	"github.com/polarismesh/polaris/store"
    30  )
    31  
    32  var (
    33  	configFileStoreFieldMapping = map[string]map[string]string{
    34  		"config_file": {
    35  			"group":     "`group`",
    36  			"file_name": "name",
    37  			"namespace": "namespace",
    38  		},
    39  		"config_file_release": {
    40  			"group":        "`group`",
    41  			"file_name":    "file_name",
    42  			"release_name": "name",
    43  		},
    44  		"config_file_group": {},
    45  	}
    46  )
    47  
    48  var _ store.ConfigFileStore = (*configFileStore)(nil)
    49  
    50  type configFileStore struct {
    51  	master *BaseDB
    52  	slave  *BaseDB
    53  }
    54  
    55  // LockConfigFile 加锁配置文件
    56  func (cf *configFileStore) LockConfigFile(tx store.Tx, file *model.ConfigFileKey) (*model.ConfigFile, error) {
    57  	if tx == nil {
    58  		return nil, ErrTxIsNil
    59  	}
    60  
    61  	dbTx := tx.GetDelegateTx().(*BaseTx)
    62  	args := []interface{}{file.Namespace, file.Group, file.Name}
    63  	lockSql := cf.baseSelectConfigFileSql() +
    64  		" WHERE namespace = ? AND `group` = ? AND name = ? AND flag = 0 FOR UPDATE"
    65  
    66  	rows, err := dbTx.Query(lockSql, args...)
    67  	if err != nil {
    68  		return nil, store.Error(err)
    69  	}
    70  	files, err := cf.transferRows(rows)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	if len(files) > 0 {
    75  		return files[0], nil
    76  	}
    77  	return nil, nil
    78  }
    79  
    80  // CreateConfigFile 创建配置文件
    81  func (cf *configFileStore) CreateConfigFileTx(tx store.Tx, file *model.ConfigFile) error {
    82  	if tx == nil {
    83  		return ErrTxIsNil
    84  	}
    85  
    86  	dbTx := tx.GetDelegateTx().(*BaseTx)
    87  	deleteSql := "DELETE FROM config_file WHERE namespace = ? AND `group` = ? AND name = ? AND flag = 1"
    88  	if _, err := dbTx.Exec(deleteSql, file.Namespace, file.Group, file.Name); err != nil {
    89  		return store.Error(err)
    90  	}
    91  
    92  	createSql := "INSERT INTO config_file( " +
    93  		" name, namespace, `group`, content, comment, format, create_time, " +
    94  		"create_by, modify_time, modify_by) " +
    95  		" VALUES " +
    96  		"(?, ?, ?, ?, ?, ?, sysdate(), ?, sysdate(), ?)"
    97  	if _, err := dbTx.Exec(createSql, file.Name, file.Namespace, file.Group,
    98  		file.Content, file.Comment, file.Format, file.CreateBy, file.ModifyBy); err != nil {
    99  		return store.Error(err)
   100  	}
   101  
   102  	if err := cf.batchCleanTags(dbTx, file); err != nil {
   103  		return store.Error(err)
   104  	}
   105  	if err := cf.batchAddTags(dbTx, file); err != nil {
   106  		return store.Error(err)
   107  	}
   108  	return nil
   109  }
   110  
   111  func (cf *configFileStore) batchAddTags(tx *BaseTx, file *model.ConfigFile) error {
   112  	if len(file.Metadata) == 0 {
   113  		return nil
   114  	}
   115  
   116  	// 添加配置标签
   117  	insertSql := "INSERT INTO config_file_tag(" +
   118  		" `key`, `value`, namespace, `group`, file_name, create_time, create_by, modify_time, modify_by) " +
   119  		" VALUES "
   120  	valuesSql := []string{}
   121  	args := []interface{}{}
   122  	for k, v := range file.Metadata {
   123  		valuesSql = append(valuesSql, " (?, ?, ?, ?, ?, sysdate(), ?, sysdate(), ?) ")
   124  		args = append(args, k, v, file.Namespace, file.Group, file.Name, file.CreateBy, file.ModifyBy)
   125  	}
   126  	insertSql = insertSql + strings.Join(valuesSql, ",")
   127  	_, err := tx.Exec(insertSql, args...)
   128  	return store.Error(err)
   129  }
   130  
   131  func (cf *configFileStore) batchCleanTags(tx *BaseTx, file *model.ConfigFile) error {
   132  	// 添加配置标签
   133  	cleanSql := "DELETE FROM config_file_tag WHERE namespace = ? AND `group` = ? AND file_name = ? "
   134  	args := []interface{}{file.Namespace, file.Group, file.Name}
   135  	_, err := tx.Exec(cleanSql, args...)
   136  	return store.Error(err)
   137  }
   138  
   139  func (cf *configFileStore) loadFileTags(tx *BaseTx, file *model.ConfigFile) error {
   140  	querySql := "SELECT `key`, `value` FROM config_file_tag WHERE namespace = ? AND " +
   141  		" `group` = ? AND file_name = ? "
   142  
   143  	rows, err := tx.Query(querySql, file.Namespace, file.Group, file.Name)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if rows == nil {
   148  		return nil
   149  	}
   150  	defer rows.Close()
   151  
   152  	file.Metadata = make(map[string]string)
   153  	for rows.Next() {
   154  		var key, value string
   155  		if err := rows.Scan(&key, &value); err != nil {
   156  			return err
   157  		}
   158  		file.Metadata[key] = value
   159  	}
   160  	return nil
   161  }
   162  
   163  // CountConfigFiles 获取一个配置文件组下的文件数量
   164  func (cfr *configFileStore) CountConfigFiles(namespace, group string) (uint64, error) {
   165  	metricsSql := "SELECT count(*) FROM config_file WHERE flag = 0 AND namespace = ? AND `group` = ?"
   166  	row := cfr.slave.QueryRow(metricsSql, namespace, group)
   167  	var total uint64
   168  	if err := row.Scan(&total); err != nil {
   169  		return 0, store.Error(err)
   170  	}
   171  	return total, nil
   172  }
   173  
   174  // GetConfigFile 获取配置文件
   175  func (cf *configFileStore) GetConfigFile(namespace, group, name string) (*model.ConfigFile, error) {
   176  	tx, err := cf.master.Begin()
   177  	if err != nil {
   178  		return nil, store.Error(err)
   179  	}
   180  	defer func() {
   181  		_ = tx.Rollback()
   182  	}()
   183  
   184  	return cf.GetConfigFileTx(NewSqlDBTx(tx), namespace, group, name)
   185  }
   186  
   187  // GetConfigFile 获取配置文件
   188  func (cf *configFileStore) GetConfigFileTx(tx store.Tx,
   189  	namespace, group, name string) (*model.ConfigFile, error) {
   190  	if tx == nil {
   191  		return nil, ErrTxIsNil
   192  	}
   193  
   194  	dbTx := tx.GetDelegateTx().(*BaseTx)
   195  	querySql := cf.baseSelectConfigFileSql() + "WHERE namespace = ? AND `group` = ? AND name = ? AND flag = 0"
   196  	rows, err := dbTx.Query(querySql, namespace, group, name)
   197  	if err != nil {
   198  		return nil, store.Error(err)
   199  	}
   200  	files, err := cf.transferRows(rows)
   201  	if err != nil {
   202  		return nil, store.Error(err)
   203  	}
   204  	if len(files) == 0 {
   205  		return nil, nil
   206  	}
   207  	if err := cf.loadFileTags(dbTx, files[0]); err != nil {
   208  		return nil, store.Error(err)
   209  	}
   210  	return files[0], nil
   211  }
   212  
   213  // UpdateConfigFile 更新配置文件
   214  func (cf *configFileStore) UpdateConfigFileTx(tx store.Tx, file *model.ConfigFile) error {
   215  	if tx == nil {
   216  		return ErrTxIsNil
   217  	}
   218  
   219  	updateSql := "UPDATE config_file SET content = ?, comment = ?, format = ?, modify_time = sysdate(), " +
   220  		" modify_by = ? WHERE namespace = ? AND `group` = ? AND name = ?"
   221  	dbTx := tx.GetDelegateTx().(*BaseTx)
   222  	_, err := dbTx.Exec(updateSql, file.Content, file.Comment, file.Format,
   223  		file.ModifyBy, file.Namespace, file.Group, file.Name)
   224  	if err != nil {
   225  		return store.Error(err)
   226  	}
   227  
   228  	if err := cf.batchCleanTags(dbTx, file); err != nil {
   229  		return store.Error(err)
   230  	}
   231  	if err := cf.batchAddTags(dbTx, file); err != nil {
   232  		return store.Error(err)
   233  	}
   234  	return nil
   235  }
   236  
   237  // DeleteConfigFileTx 删除配置文件
   238  func (cf *configFileStore) DeleteConfigFileTx(tx store.Tx, namespace, group, name string) error {
   239  	if tx == nil {
   240  		return ErrTxIsNil
   241  	}
   242  
   243  	deleteSql := "UPDATE config_file SET flag = 1 WHERE namespace = ? AND `group` = ? AND name = ?"
   244  	dbTx := tx.GetDelegateTx().(*BaseTx)
   245  	if _, err := dbTx.Exec(deleteSql, namespace, group, name); err != nil {
   246  		return store.Error(err)
   247  	}
   248  	return nil
   249  }
   250  
   251  // QueryConfigFiles 翻页查询配置文件,group、name可为模糊匹配
   252  func (cf *configFileStore) QueryConfigFiles(filter map[string]string, offset, limit uint32) (uint32, []*model.ConfigFile, error) {
   253  
   254  	countSql := "SELECT COUNT(*) FROM config_file WHERE flag = 0 "
   255  	querySql := cf.baseSelectConfigFileSql() + " WHERE flag = 0 "
   256  
   257  	args := make([]interface{}, 0, len(filter))
   258  	searchQuery := make([]string, 0, len(filter))
   259  
   260  	for k, v := range filter {
   261  		if v, ok := configFileStoreFieldMapping["config_file"][k]; ok {
   262  			k = v
   263  		}
   264  		if utils.IsWildName(v) {
   265  			searchQuery = append(searchQuery, k+" LIKE ? ")
   266  		} else {
   267  			searchQuery = append(searchQuery, k+" = ? ")
   268  		}
   269  		args = append(args, utils.ParseWildNameForSql(v))
   270  	}
   271  
   272  	if len(searchQuery) > 0 {
   273  		countSql = countSql + " AND "
   274  		querySql = querySql + " AND "
   275  	}
   276  	countSql = countSql + (strings.Join(searchQuery, " AND "))
   277  
   278  	var count uint32
   279  	err := cf.master.QueryRow(countSql, args...).Scan(&count)
   280  	if err != nil {
   281  		log.Error("[Config][Storage] query config files", zap.String("count-sql", countSql), zap.Error(err))
   282  		return 0, nil, store.Error(err)
   283  	}
   284  
   285  	querySql = querySql + (strings.Join(searchQuery, " AND ")) + " ORDER BY id DESC LIMIT ?, ? "
   286  
   287  	args = append(args, offset, limit)
   288  	rows, err := cf.master.Query(querySql, args...)
   289  	if err != nil {
   290  		log.Error("[Config][Storage] query config files", zap.String("query-sql", countSql), zap.Error(err))
   291  		return 0, nil, store.Error(err)
   292  	}
   293  
   294  	files, err := cf.transferRows(rows)
   295  	if err != nil {
   296  		return 0, nil, store.Error(err)
   297  	}
   298  
   299  	err = cf.slave.processWithTransaction("batch-load-file-tags", func(tx *BaseTx) error {
   300  		for i := range files {
   301  			item := files[i]
   302  			if err := cf.loadFileTags(tx, item); err != nil {
   303  				return err
   304  			}
   305  		}
   306  		return nil
   307  	})
   308  	if err != nil {
   309  		return 0, nil, store.Error(err)
   310  	}
   311  
   312  	return count, files, nil
   313  }
   314  
   315  // CountConfigFileEachGroup
   316  func (cf *configFileStore) CountConfigFileEachGroup() (map[string]map[string]int64, error) {
   317  	metricsSql := "SELECT namespace, `group`, count(name) FROM config_file WHERE flag = 0 GROUP by namespace, `group`"
   318  	rows, err := cf.slave.Query(metricsSql)
   319  	if err != nil {
   320  		return nil, store.Error(err)
   321  	}
   322  
   323  	defer func() {
   324  		_ = rows.Close()
   325  	}()
   326  
   327  	ret := map[string]map[string]int64{}
   328  	for rows.Next() {
   329  		var (
   330  			namespce string
   331  			group    string
   332  			cnt      int64
   333  		)
   334  
   335  		if err := rows.Scan(&namespce, &group, &cnt); err != nil {
   336  			return nil, err
   337  		}
   338  		if _, ok := ret[namespce]; !ok {
   339  			ret[namespce] = map[string]int64{}
   340  		}
   341  		ret[namespce][group] = cnt
   342  	}
   343  
   344  	return ret, nil
   345  }
   346  
   347  func (cf *configFileStore) baseSelectConfigFileSql() string {
   348  	return "SELECT id, name, namespace, `group`, content, IFNULL(comment, ''), format, " +
   349  		" UNIX_TIMESTAMP(create_time), IFNULL(create_by, ''), UNIX_TIMESTAMP(modify_time), " +
   350  		" IFNULL(modify_by, '') FROM config_file "
   351  }
   352  
   353  func (cf *configFileStore) hardDeleteConfigFile(namespace, group, name string) error {
   354  	deleteSql := "DELETE FROM config_file WHERE namespace = ? AND `group` = ? AND name = ? AND flag = 1"
   355  	_, err := cf.master.Exec(deleteSql, namespace, group, name)
   356  	if err != nil {
   357  		return store.Error(err)
   358  	}
   359  
   360  	return nil
   361  }
   362  
   363  func (cf *configFileStore) transferRows(rows *sql.Rows) ([]*model.ConfigFile, error) {
   364  	if rows == nil {
   365  		return nil, nil
   366  	}
   367  	defer rows.Close()
   368  
   369  	var (
   370  		files = make([]*model.ConfigFile, 0, 32)
   371  	)
   372  
   373  	for rows.Next() {
   374  		file := &model.ConfigFile{
   375  			Metadata: map[string]string{},
   376  		}
   377  		var ctime, mtime int64
   378  		if err := rows.Scan(&file.Id, &file.Name, &file.Namespace, &file.Group, &file.Content, &file.Comment,
   379  			&file.Format, &ctime, &file.CreateBy, &mtime, &file.ModifyBy); err != nil {
   380  			return nil, err
   381  		}
   382  		file.CreateTime = time.Unix(ctime, 0)
   383  		file.ModifyTime = time.Unix(mtime, 0)
   384  		files = append(files, file)
   385  	}
   386  
   387  	if err := rows.Err(); err != nil {
   388  		return nil, err
   389  	}
   390  	return files, nil
   391  }