blob: 1d73a1c2a2717b00a711867a3c9b72461a4c6913 [file] [log] [blame]
khenaidoo26721882021-08-11 17:42:52 -04001// 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.
16package snap
17
18import (
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
39const (
40 snapSuffix = ".snap"
41)
42
43var (
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
57type Snapshotter struct {
58 dir string
59}
60
61func New(dir string) *Snapshotter {
62 return &Snapshotter{
63 dir: dir,
64 }
65}
66
67func (s *Snapshotter) SaveSnap(snapshot raftpb.Snapshot) error {
68 if raft.IsEmptySnap(snapshot) {
69 return nil
70 }
71 return s.save(&snapshot)
72}
73
74func (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
99func (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.
104func (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.
117func (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
131func 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.
141func 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.
180func (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
202func 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
218func 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
227func (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
241func (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}