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 15package snap 16 17import ( 18 "errors" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "time" 25 26 "go.etcd.io/etcd/pkg/fileutil" 27 28 humanize "github.com/dustin/go-humanize" 29 "go.uber.org/zap" 30) 31 32var ErrNoDBSnapshot = errors.New("snap: snapshot file doesn't exist") 33 34// SaveDBFrom saves snapshot of the database from the given reader. It 35// guarantees the save operation is atomic. 36func (s *Snapshotter) SaveDBFrom(r io.Reader, id uint64) (int64, error) { 37 start := time.Now() 38 39 f, err := ioutil.TempFile(s.dir, "tmp") 40 if err != nil { 41 return 0, err 42 } 43 var n int64 44 n, err = io.Copy(f, r) 45 if err == nil { 46 fsyncStart := time.Now() 47 err = fileutil.Fsync(f) 48 snapDBFsyncSec.Observe(time.Since(fsyncStart).Seconds()) 49 } 50 f.Close() 51 if err != nil { 52 os.Remove(f.Name()) 53 return n, err 54 } 55 fn := s.dbFilePath(id) 56 if fileutil.Exist(fn) { 57 os.Remove(f.Name()) 58 return n, nil 59 } 60 err = os.Rename(f.Name(), fn) 61 if err != nil { 62 os.Remove(f.Name()) 63 return n, err 64 } 65 66 s.lg.Info( 67 "saved database snapshot to disk", 68 zap.String("path", fn), 69 zap.Int64("bytes", n), 70 zap.String("size", humanize.Bytes(uint64(n))), 71 ) 72 73 snapDBSaveSec.Observe(time.Since(start).Seconds()) 74 return n, nil 75} 76 77// DBFilePath returns the file path for the snapshot of the database with 78// given id. If the snapshot does not exist, it returns error. 79func (s *Snapshotter) DBFilePath(id uint64) (string, error) { 80 if _, err := fileutil.ReadDir(s.dir); err != nil { 81 return "", err 82 } 83 fn := s.dbFilePath(id) 84 if fileutil.Exist(fn) { 85 return fn, nil 86 } 87 if s.lg != nil { 88 s.lg.Warn( 89 "failed to find [SNAPSHOT-INDEX].snap.db", 90 zap.Uint64("snapshot-index", id), 91 zap.String("snapshot-file-path", fn), 92 zap.Error(ErrNoDBSnapshot), 93 ) 94 } 95 return "", ErrNoDBSnapshot 96} 97 98func (s *Snapshotter) dbFilePath(id uint64) string { 99 return filepath.Join(s.dir, fmt.Sprintf("%016x.snap.db", id)) 100} 101