github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/relay/meta.go (about)

     1  // Copyright 2019 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package relay
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"sync"
    22  
    23  	"github.com/BurntSushi/toml"
    24  	"github.com/go-mysql-org/go-mysql/mysql"
    25  	"github.com/pingcap/tiflow/dm/pkg/gtid"
    26  	"github.com/pingcap/tiflow/dm/pkg/terror"
    27  	"github.com/pingcap/tiflow/dm/pkg/utils"
    28  )
    29  
    30  var (
    31  	minUUIDSuffix = 1
    32  	minCheckpoint = mysql.Position{Pos: 4}
    33  )
    34  
    35  // Meta represents relay log meta information for sync source
    36  // when re-syncing, we should reload meta info to guarantee continuous transmission
    37  // in order to support master-slave switching, Meta should support switching binlog meta info to newer master
    38  // should support the case, where switching from A to B, then switching from B back to A.
    39  type Meta interface {
    40  	// Load loads meta information for the recently active server
    41  	Load() error
    42  
    43  	// AdjustWithStartPos adjusts current pos / GTID with start pos
    44  	// if current pos / GTID is meaningless, update to start pos or last pos when start pos is meaningless
    45  	// else do nothing
    46  	AdjustWithStartPos(binlogName string, binlogGTID string, enableGTID bool, latestBinlogName string, latestBinlogGTID string) (bool, error)
    47  
    48  	// Save saves meta information
    49  	Save(pos mysql.Position, gset mysql.GTIDSet) error
    50  
    51  	// Flush flushes meta information
    52  	Flush() error
    53  
    54  	// Dirty checks whether meta in memory is dirty (need to Flush)
    55  	Dirty() bool
    56  
    57  	// AddDir adds relay log subdirectory for a new server. The name of new subdirectory
    58  	// consists of the server_uuid of new server and a suffix.
    59  	// if suffix is not zero value, add sub relay directory with suffix (bound to a new source)
    60  	// otherwise the added sub relay directory's suffix is incremented (master/slave switch)
    61  	// after sub relay directory added, the internal binlog pos should be reset
    62  	// and binlog pos will be set again when new binlog events received
    63  	// if set @newPos / @newGTID, old value will be replaced
    64  	AddDir(serverUUID string, newPos *mysql.Position, newGTID mysql.GTIDSet, suffix int) error
    65  
    66  	// Pos returns current (UUID with suffix, Position) pair
    67  	Pos() (string, mysql.Position)
    68  
    69  	// GTID returns current (UUID with suffix, GTID) pair
    70  	GTID() (string, mysql.GTIDSet)
    71  
    72  	// SubDir returns the name of current relay log subdirectory.
    73  	SubDir() string
    74  
    75  	// TrimUUIDIndexFile trim invalid relay log subdirectories from memory and update the server-uuid.index file
    76  	// return trimmed result.
    77  	TrimUUIDIndexFile() ([]string, error)
    78  
    79  	// Dir returns the full path of relay log subdirectory.
    80  	Dir() string
    81  
    82  	// String returns string representation of current meta info
    83  	String() string
    84  }
    85  
    86  // LocalMeta implements Meta by save info in local.
    87  type LocalMeta struct {
    88  	sync.RWMutex
    89  	flavor        string
    90  	baseDir       string
    91  	uuidIndexPath string
    92  	currentSubDir string
    93  	subDirs       []string
    94  	gset          mysql.GTIDSet
    95  	emptyGSet     mysql.GTIDSet
    96  	dirty         bool
    97  
    98  	BinLogName string `toml:"binlog-name" json:"binlog-name"`
    99  	BinLogPos  uint32 `toml:"binlog-pos" json:"binlog-pos"`
   100  	BinlogGTID string `toml:"binlog-gtid" json:"binlog-gtid"`
   101  }
   102  
   103  // NewLocalMeta creates a new LocalMeta.
   104  func NewLocalMeta(flavor, baseDir string) Meta {
   105  	lm := &LocalMeta{
   106  		flavor:        flavor,
   107  		baseDir:       baseDir,
   108  		uuidIndexPath: filepath.Join(baseDir, utils.UUIDIndexFilename),
   109  		currentSubDir: "",
   110  		subDirs:       make([]string, 0),
   111  		dirty:         false,
   112  		BinLogName:    minCheckpoint.Name,
   113  		BinLogPos:     minCheckpoint.Pos,
   114  		BinlogGTID:    "",
   115  	}
   116  	lm.emptyGSet, _ = gtid.ParserGTID(flavor, "")
   117  	return lm
   118  }
   119  
   120  // Load implements Meta.Load.
   121  func (lm *LocalMeta) Load() error {
   122  	lm.Lock()
   123  	defer lm.Unlock()
   124  
   125  	subDirs, err := utils.ParseUUIDIndex(lm.uuidIndexPath)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	err = lm.verifySubDirs(subDirs)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	if len(subDirs) > 0 {
   136  		// update to the latest
   137  		err = lm.updateCurrentSubDir(subDirs[len(subDirs)-1])
   138  		if err != nil {
   139  			return err
   140  		}
   141  	}
   142  	lm.subDirs = subDirs
   143  
   144  	err = lm.loadMetaData()
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  // AdjustWithStartPos implements Meta.AdjustWithStartPos, return whether adjusted.
   153  func (lm *LocalMeta) AdjustWithStartPos(binlogName string, binlogGTID string, enableGTID bool, latestBinlogName string, latestBinlogGTID string) (bool, error) {
   154  	lm.Lock()
   155  	defer lm.Unlock()
   156  
   157  	// check whether already have meaningful pos
   158  	if len(lm.currentSubDir) > 0 {
   159  		_, suffix, err := utils.ParseRelaySubDir(lm.currentSubDir)
   160  		if err != nil {
   161  			return false, err
   162  		}
   163  		currPos := mysql.Position{Name: lm.BinLogName, Pos: lm.BinLogPos}
   164  		if suffix != minUUIDSuffix || currPos.Compare(minCheckpoint) > 0 || len(lm.BinlogGTID) > 0 {
   165  			return false, nil // current pos is meaningful, do nothing
   166  		}
   167  	}
   168  
   169  	gset := lm.emptyGSet.Clone()
   170  	var err error
   171  
   172  	if enableGTID {
   173  		if len(binlogGTID) == 0 {
   174  			binlogGTID = latestBinlogGTID
   175  			binlogName = latestBinlogName
   176  		}
   177  		gset, err = gtid.ParserGTID(lm.flavor, binlogGTID)
   178  		if err != nil {
   179  			return false, terror.Annotatef(err, "relay-binlog-gtid %s", binlogGTID)
   180  		}
   181  	} else {
   182  		if len(binlogName) == 0 { // no meaningful start pos specified
   183  			binlogGTID = latestBinlogGTID
   184  			binlogName = latestBinlogName
   185  		} else if !utils.VerifyFilename(binlogName) {
   186  			return false, terror.ErrRelayBinlogNameNotValid.Generate(binlogName)
   187  		}
   188  	}
   189  
   190  	lm.BinLogName = binlogName
   191  	lm.BinLogPos = minCheckpoint.Pos // always set pos to 4
   192  	lm.BinlogGTID = binlogGTID
   193  	lm.gset = gset
   194  
   195  	return true, lm.doFlush()
   196  }
   197  
   198  // Save implements Meta.Save.
   199  func (lm *LocalMeta) Save(pos mysql.Position, gset mysql.GTIDSet) error {
   200  	lm.Lock()
   201  	defer lm.Unlock()
   202  
   203  	if len(lm.currentSubDir) == 0 {
   204  		return terror.ErrRelayNoCurrentUUID.Generate()
   205  	}
   206  
   207  	lm.BinLogName = pos.Name
   208  	lm.BinLogPos = pos.Pos
   209  	if gset == nil {
   210  		lm.BinlogGTID = ""
   211  	} else {
   212  		lm.BinlogGTID = gset.String()
   213  		lm.gset = gset.Clone() // need to clone and set, in order to avoid the local meta's gset and the input gset referencing the same object, causing contentions later
   214  	}
   215  
   216  	lm.dirty = true
   217  
   218  	return nil
   219  }
   220  
   221  // Flush implements Meta.Flush.
   222  func (lm *LocalMeta) Flush() error {
   223  	lm.Lock()
   224  	defer lm.Unlock()
   225  
   226  	return lm.doFlush()
   227  }
   228  
   229  // doFlush does the real flushing.
   230  func (lm *LocalMeta) doFlush() error {
   231  	if len(lm.currentSubDir) == 0 {
   232  		return terror.ErrRelayNoCurrentUUID.Generate()
   233  	}
   234  
   235  	var buf bytes.Buffer
   236  	enc := toml.NewEncoder(&buf)
   237  	err := enc.Encode(lm)
   238  	if err != nil {
   239  		return terror.ErrRelayFlushLocalMeta.Delegate(err)
   240  	}
   241  
   242  	filename := filepath.Join(lm.baseDir, lm.currentSubDir, utils.MetaFilename)
   243  	err = utils.WriteFileAtomic(filename, buf.Bytes(), 0o644)
   244  	if err != nil {
   245  		return terror.ErrRelayFlushLocalMeta.Delegate(err)
   246  	}
   247  
   248  	lm.dirty = false
   249  
   250  	return nil
   251  }
   252  
   253  // Dirty implements Meta.Dirty.
   254  func (lm *LocalMeta) Dirty() bool {
   255  	lm.RLock()
   256  	defer lm.RUnlock()
   257  
   258  	return lm.dirty
   259  }
   260  
   261  // Dir implements Meta.Dir.
   262  func (lm *LocalMeta) Dir() string {
   263  	lm.RLock()
   264  	defer lm.RUnlock()
   265  
   266  	return filepath.Join(lm.baseDir, lm.currentSubDir)
   267  }
   268  
   269  // AddDir implements Meta.AddDir.
   270  func (lm *LocalMeta) AddDir(serverUUID string, newPos *mysql.Position, newGTID mysql.GTIDSet, uuidSuffix int) error {
   271  	lm.Lock()
   272  	defer lm.Unlock()
   273  
   274  	var newSubDir string
   275  
   276  	if len(lm.currentSubDir) == 0 {
   277  		// no UUID exists yet, simply add it
   278  		if uuidSuffix == 0 {
   279  			newSubDir = utils.AddSuffixForUUID(serverUUID, minUUIDSuffix)
   280  		} else {
   281  			newSubDir = utils.AddSuffixForUUID(serverUUID, uuidSuffix)
   282  		}
   283  	} else {
   284  		_, suffix, err := utils.ParseRelaySubDir(lm.currentSubDir)
   285  		if err != nil {
   286  			return err
   287  		}
   288  		// even newSubDir == currentSubDir, we still append it (for some cases, like `RESET MASTER`)
   289  		newSubDir = utils.AddSuffixForUUID(serverUUID, suffix+1)
   290  	}
   291  
   292  	// flush previous meta
   293  	if lm.dirty {
   294  		err := lm.doFlush()
   295  		if err != nil {
   296  			return err
   297  		}
   298  	}
   299  
   300  	// make sub dir for UUID
   301  	err := os.Mkdir(filepath.Join(lm.baseDir, newSubDir), 0o744)
   302  	if err != nil {
   303  		return terror.ErrRelayMkdir.Delegate(err)
   304  	}
   305  
   306  	// update UUID index file
   307  	uuids := lm.subDirs
   308  	uuids = append(uuids, newSubDir)
   309  	err = lm.updateIndexFile(uuids)
   310  	if err != nil {
   311  		return err
   312  	}
   313  
   314  	// update current UUID
   315  	lm.currentSubDir = newSubDir
   316  	lm.subDirs = uuids
   317  
   318  	if newPos != nil {
   319  		lm.BinLogName = newPos.Name
   320  		lm.BinLogPos = newPos.Pos
   321  	} else {
   322  		// reset binlog pos, will be set again when new binlog events received from master
   323  		// not reset GTID, it will be used to continue the syncing
   324  		lm.BinLogName = minCheckpoint.Name
   325  		lm.BinLogPos = minCheckpoint.Pos
   326  	}
   327  
   328  	if newGTID != nil {
   329  		lm.gset = newGTID.Clone() // need to clone and set, in order to avoid the local meta's gset and the input newGTID referencing the same object, causing contentions later
   330  		lm.BinlogGTID = newGTID.String()
   331  	} // if newGTID == nil, keep GTID not changed
   332  
   333  	// flush new meta to file
   334  	return lm.doFlush()
   335  }
   336  
   337  // Pos implements Meta.Pos.
   338  func (lm *LocalMeta) Pos() (string, mysql.Position) {
   339  	lm.RLock()
   340  	defer lm.RUnlock()
   341  
   342  	return lm.currentSubDir, mysql.Position{Name: lm.BinLogName, Pos: lm.BinLogPos}
   343  }
   344  
   345  // GTID implements Meta.GTID.
   346  func (lm *LocalMeta) GTID() (string, mysql.GTIDSet) {
   347  	lm.RLock()
   348  	defer lm.RUnlock()
   349  
   350  	if lm.gset != nil {
   351  		return lm.currentSubDir, lm.gset.Clone()
   352  	}
   353  	return lm.currentSubDir, nil
   354  }
   355  
   356  // SubDir implements Meta.SubDir.
   357  func (lm *LocalMeta) SubDir() string {
   358  	lm.RLock()
   359  	defer lm.RUnlock()
   360  	return lm.currentSubDir
   361  }
   362  
   363  // TrimUUIDIndexFile implements Meta.TrimUUIDIndexFile.
   364  func (lm *LocalMeta) TrimUUIDIndexFile() ([]string, error) {
   365  	lm.Lock()
   366  	defer lm.Unlock()
   367  
   368  	kept := make([]string, 0, len(lm.subDirs))
   369  	trimmed := make([]string, 0)
   370  	for _, subDir := range lm.subDirs {
   371  		// now, only check if the sub dir exists
   372  		fp := filepath.Join(lm.baseDir, subDir)
   373  		if utils.IsDirExists(fp) {
   374  			kept = append(kept, subDir)
   375  		} else {
   376  			trimmed = append(trimmed, subDir)
   377  		}
   378  	}
   379  
   380  	if len(trimmed) == 0 {
   381  		return nil, nil
   382  	}
   383  
   384  	err := lm.updateIndexFile(kept)
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	// currentSubDir should be not changed
   390  	lm.subDirs = kept
   391  	return trimmed, nil
   392  }
   393  
   394  // String implements Meta.String.
   395  func (lm *LocalMeta) String() string {
   396  	uuid, pos := lm.Pos()
   397  	_, gs := lm.GTID()
   398  	return fmt.Sprintf("master-uuid = %s, relay-binlog = %v, relay-binlog-gtid = %v", uuid, pos, gs)
   399  }
   400  
   401  // updateIndexFile updates the content of server-uuid.index file.
   402  func (lm *LocalMeta) updateIndexFile(uuids []string) error {
   403  	var buf bytes.Buffer
   404  	for _, uuid := range uuids {
   405  		buf.WriteString(uuid)
   406  		buf.WriteString("\n")
   407  	}
   408  
   409  	err := utils.WriteFileAtomic(lm.uuidIndexPath, buf.Bytes(), 0o644)
   410  	return terror.ErrRelayUpdateIndexFile.Delegate(err, lm.uuidIndexPath)
   411  }
   412  
   413  func (lm *LocalMeta) verifySubDirs(uuids []string) error {
   414  	previousSuffix := 0
   415  	for _, uuid := range uuids {
   416  		_, suffix, err := utils.ParseRelaySubDir(uuid)
   417  		if err != nil {
   418  			return terror.Annotatef(err, "UUID %s", uuid)
   419  		}
   420  		if previousSuffix > 0 {
   421  			if previousSuffix+1 != suffix {
   422  				return terror.ErrRelayUUIDSuffixNotValid.Generate(uuid, suffix, previousSuffix)
   423  			}
   424  		}
   425  		previousSuffix = suffix
   426  	}
   427  
   428  	return nil
   429  }
   430  
   431  // updateCurrentSubDir updates current relay log subdirectory.
   432  func (lm *LocalMeta) updateCurrentSubDir(uuid string) error {
   433  	_, suffix, err := utils.ParseRelaySubDir(uuid)
   434  	if err != nil {
   435  		return err
   436  	}
   437  
   438  	if len(lm.currentSubDir) > 0 {
   439  		_, previousSuffix, err := utils.ParseRelaySubDir(lm.currentSubDir)
   440  		if err != nil {
   441  			return err // should not happen
   442  		}
   443  		if previousSuffix > suffix {
   444  			return terror.ErrRelayUUIDSuffixLessThanPrev.Generate(lm.currentSubDir, uuid)
   445  		}
   446  	}
   447  
   448  	lm.currentSubDir = uuid
   449  	return nil
   450  }
   451  
   452  // loadMetaData loads meta information from meta data file.
   453  func (lm *LocalMeta) loadMetaData() error {
   454  	lm.gset = lm.emptyGSet.Clone()
   455  
   456  	if len(lm.currentSubDir) == 0 {
   457  		return nil
   458  	}
   459  
   460  	filename := filepath.Join(lm.baseDir, lm.currentSubDir, utils.MetaFilename)
   461  
   462  	fd, err := os.Open(filename)
   463  	if os.IsNotExist(err) {
   464  		return nil
   465  	} else if err != nil {
   466  		return terror.ErrRelayLoadMetaData.Delegate(err)
   467  	}
   468  	defer fd.Close()
   469  
   470  	_, err = toml.DecodeReader(fd, lm)
   471  	if err != nil {
   472  		return terror.ErrRelayLoadMetaData.Delegate(err)
   473  	}
   474  
   475  	if len(lm.BinlogGTID) != 0 {
   476  		gset, err := gtid.ParserGTID("", lm.BinlogGTID)
   477  		if err != nil {
   478  			return terror.ErrRelayLoadMetaData.Delegate(err)
   479  		}
   480  		lm.gset = gset
   481  	}
   482  
   483  	return nil
   484  }