go.etcd.io/etcd@v3.3.27+incompatible/snap/snapshotter.go (about)

     1  // Copyright 2015 The etcd Authors
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package snap stores raft nodes' states with snapshots.
    16  package snap
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"hash/crc32"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  	"time"
    29  
    30  	pioutil "github.com/coreos/etcd/pkg/ioutil"
    31  	"github.com/coreos/etcd/pkg/pbutil"
    32  	"github.com/coreos/etcd/raft"
    33  	"github.com/coreos/etcd/raft/raftpb"
    34  	"github.com/coreos/etcd/snap/snappb"
    35  	"github.com/coreos/etcd/wal/walpb"
    36  	"github.com/coreos/pkg/capnslog"
    37  )
    38  
    39  const (
    40  	snapSuffix = ".snap"
    41  )
    42  
    43  var (
    44  	plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "snap")
    45  
    46  	ErrNoSnapshot    = errors.New("snap: no available snapshot")
    47  	ErrEmptySnapshot = errors.New("snap: empty snapshot")
    48  	ErrCRCMismatch   = errors.New("snap: crc mismatch")
    49  	crcTable         = crc32.MakeTable(crc32.Castagnoli)
    50  
    51  	// A map of valid files that can be present in the snap folder.
    52  	validFiles = map[string]bool{
    53  		"db": true,
    54  	}
    55  )
    56  
    57  type Snapshotter struct {
    58  	dir string
    59  }
    60  
    61  func New(dir string) *Snapshotter {
    62  	return &Snapshotter{
    63  		dir: dir,
    64  	}
    65  }
    66  
    67  func (s *Snapshotter) SaveSnap(snapshot raftpb.Snapshot) error {
    68  	if raft.IsEmptySnap(snapshot) {
    69  		return nil
    70  	}
    71  	return s.save(&snapshot)
    72  }
    73  
    74  func (s *Snapshotter) save(snapshot *raftpb.Snapshot) error {
    75  	start := time.Now()
    76  
    77  	fname := fmt.Sprintf("%016x-%016x%s", snapshot.Metadata.Term, snapshot.Metadata.Index, snapSuffix)
    78  	b := pbutil.MustMarshal(snapshot)
    79  	crc := crc32.Update(0, crcTable, b)
    80  	snap := snappb.Snapshot{Crc: crc, Data: b}
    81  	d, err := snap.Marshal()
    82  	if err != nil {
    83  		return err
    84  	}
    85  	marshallingDurations.Observe(float64(time.Since(start)) / float64(time.Second))
    86  
    87  	err = pioutil.WriteAndSyncFile(filepath.Join(s.dir, fname), d, 0666)
    88  	if err == nil {
    89  		saveDurations.Observe(float64(time.Since(start)) / float64(time.Second))
    90  	} else {
    91  		err1 := os.Remove(filepath.Join(s.dir, fname))
    92  		if err1 != nil {
    93  			plog.Errorf("failed to remove broken snapshot file %s", filepath.Join(s.dir, fname))
    94  		}
    95  	}
    96  	return err
    97  }
    98  
    99  func (s *Snapshotter) Load() (*raftpb.Snapshot, error) {
   100  	return s.loadMatching(func(*raftpb.Snapshot) bool { return true })
   101  }
   102  
   103  // LoadNewestAvailable loads the newest snapshot available that is in walSnaps.
   104  func (s *Snapshotter) LoadNewestAvailable(walSnaps []walpb.Snapshot) (*raftpb.Snapshot, error) {
   105  	return s.loadMatching(func(snapshot *raftpb.Snapshot) bool {
   106  		m := snapshot.Metadata
   107  		for i := len(walSnaps) - 1; i >= 0; i-- {
   108  			if m.Term == walSnaps[i].Term && m.Index == walSnaps[i].Index {
   109  				return true
   110  			}
   111  		}
   112  		return false
   113  	})
   114  }
   115  
   116  // loadMatching returns the newest snapshot where matchFn returns true.
   117  func (s *Snapshotter) loadMatching(matchFn func(*raftpb.Snapshot) bool) (*raftpb.Snapshot, error) {
   118  	names, err := s.snapNames()
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	var snap *raftpb.Snapshot
   123  	for _, name := range names {
   124  		if snap, err = loadSnap(s.dir, name); err == nil && matchFn(snap) {
   125  			return snap, nil
   126  		}
   127  	}
   128  	return nil, ErrNoSnapshot
   129  }
   130  
   131  func loadSnap(dir, name string) (*raftpb.Snapshot, error) {
   132  	fpath := filepath.Join(dir, name)
   133  	snap, err := Read(fpath)
   134  	if err != nil {
   135  		renameBroken(fpath)
   136  	}
   137  	return snap, err
   138  }
   139  
   140  // Read reads the snapshot named by snapname and returns the snapshot.
   141  func Read(snapname string) (*raftpb.Snapshot, error) {
   142  	b, err := ioutil.ReadFile(snapname)
   143  	if err != nil {
   144  		plog.Errorf("cannot read file %v: %v", snapname, err)
   145  		return nil, err
   146  	}
   147  
   148  	if len(b) == 0 {
   149  		plog.Errorf("unexpected empty snapshot")
   150  		return nil, ErrEmptySnapshot
   151  	}
   152  
   153  	var serializedSnap snappb.Snapshot
   154  	if err = serializedSnap.Unmarshal(b); err != nil {
   155  		plog.Errorf("corrupted snapshot file %v: %v", snapname, err)
   156  		return nil, err
   157  	}
   158  
   159  	if len(serializedSnap.Data) == 0 || serializedSnap.Crc == 0 {
   160  		plog.Errorf("unexpected empty snapshot")
   161  		return nil, ErrEmptySnapshot
   162  	}
   163  
   164  	crc := crc32.Update(0, crcTable, serializedSnap.Data)
   165  	if crc != serializedSnap.Crc {
   166  		plog.Errorf("corrupted snapshot file %v: crc mismatch", snapname)
   167  		return nil, ErrCRCMismatch
   168  	}
   169  
   170  	var snap raftpb.Snapshot
   171  	if err = snap.Unmarshal(serializedSnap.Data); err != nil {
   172  		plog.Errorf("corrupted snapshot file %v: %v", snapname, err)
   173  		return nil, err
   174  	}
   175  	return &snap, nil
   176  }
   177  
   178  // snapNames returns the filename of the snapshots in logical time order (from newest to oldest).
   179  // If there is no available snapshots, an ErrNoSnapshot will be returned.
   180  func (s *Snapshotter) snapNames() ([]string, error) {
   181  	dir, err := os.Open(s.dir)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	defer dir.Close()
   186  	names, err := dir.Readdirnames(-1)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	names, err = s.cleanupSnapdir(names)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	snaps := checkSuffix(names)
   195  	if len(snaps) == 0 {
   196  		return nil, ErrNoSnapshot
   197  	}
   198  	sort.Sort(sort.Reverse(sort.StringSlice(snaps)))
   199  	return snaps, nil
   200  }
   201  
   202  func checkSuffix(names []string) []string {
   203  	snaps := []string{}
   204  	for i := range names {
   205  		if strings.HasSuffix(names[i], snapSuffix) {
   206  			snaps = append(snaps, names[i])
   207  		} else {
   208  			// If we find a file which is not a snapshot then check if it's
   209  			// a vaild file. If not throw out a warning.
   210  			if _, ok := validFiles[names[i]]; !ok {
   211  				plog.Warningf("skipped unexpected non snapshot file %v", names[i])
   212  			}
   213  		}
   214  	}
   215  	return snaps
   216  }
   217  
   218  func renameBroken(path string) {
   219  	brokenPath := path + ".broken"
   220  	if err := os.Rename(path, brokenPath); err != nil {
   221  		plog.Warningf("cannot rename broken snapshot file %v to %v: %v", path, brokenPath, err)
   222  	}
   223  }
   224  
   225  // cleanupSnapdir removes any files that should not be in the snapshot directory:
   226  // - db.tmp prefixed files that can be orphaned by defragmentation
   227  func (s *Snapshotter) cleanupSnapdir(filenames []string) (names []string, err error) {
   228  	for _, filename := range filenames {
   229  		if strings.HasPrefix(filename, "db.tmp") {
   230  			plog.Infof("found orphaned defragmentation file; deleting: %s", filename)
   231  			if rmErr := os.Remove(filepath.Join(s.dir, filename)); rmErr != nil && !os.IsNotExist(rmErr) {
   232  				return nil, fmt.Errorf("failed to remove orphaned defragmentation file %s: %v", filename, rmErr)
   233  			}
   234  			continue
   235  		}
   236  		names = append(names, filename)
   237  	}
   238  	return names, nil
   239  }
   240  
   241  func (s *Snapshotter) ReleaseSnapDBs(snap raftpb.Snapshot) error {
   242  	dir, err := os.Open(s.dir)
   243  	if err != nil {
   244  		return err
   245  	}
   246  	defer dir.Close()
   247  	filenames, err := dir.Readdirnames(-1)
   248  	if err != nil {
   249  		return err
   250  	}
   251  	for _, filename := range filenames {
   252  		if strings.HasSuffix(filename, ".snap.db") {
   253  			hexIndex := strings.TrimSuffix(filepath.Base(filename), ".snap.db")
   254  			index, err := strconv.ParseUint(hexIndex, 16, 64)
   255  			if err != nil {
   256  				plog.Warningf("failed to parse index from filename: %s (%v)", filename, err)
   257  				continue
   258  			}
   259  			if index < snap.Metadata.Index {
   260  				plog.Infof("found orphaned .snap.db file; deleting %q", filename)
   261  				if rmErr := os.Remove(filepath.Join(s.dir, filename)); rmErr != nil && !os.IsNotExist(rmErr) {
   262  					plog.Warningf("failed to remove orphaned .snap.db file: %s (%v)", filename, rmErr)
   263  				}
   264  			}
   265  		}
   266  	}
   267  	return nil
   268  }