blob: 007555921295457c9e1bd8951c3bb6678a393340 [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001// 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 "strings"
27 "time"
28
29 pioutil "github.com/coreos/etcd/pkg/ioutil"
30 "github.com/coreos/etcd/pkg/pbutil"
31 "github.com/coreos/etcd/raft"
32 "github.com/coreos/etcd/raft/raftpb"
33 "github.com/coreos/etcd/snap/snappb"
34
35 "github.com/coreos/pkg/capnslog"
36)
37
38const (
39 snapSuffix = ".snap"
40)
41
42var (
43 plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "snap")
44
45 ErrNoSnapshot = errors.New("snap: no available snapshot")
46 ErrEmptySnapshot = errors.New("snap: empty snapshot")
47 ErrCRCMismatch = errors.New("snap: crc mismatch")
48 crcTable = crc32.MakeTable(crc32.Castagnoli)
49
50 // A map of valid files that can be present in the snap folder.
51 validFiles = map[string]bool{
52 "db": true,
53 }
54)
55
56type Snapshotter struct {
57 dir string
58}
59
60func New(dir string) *Snapshotter {
61 return &Snapshotter{
62 dir: dir,
63 }
64}
65
66func (s *Snapshotter) SaveSnap(snapshot raftpb.Snapshot) error {
67 if raft.IsEmptySnap(snapshot) {
68 return nil
69 }
70 return s.save(&snapshot)
71}
72
73func (s *Snapshotter) save(snapshot *raftpb.Snapshot) error {
74 start := time.Now()
75
76 fname := fmt.Sprintf("%016x-%016x%s", snapshot.Metadata.Term, snapshot.Metadata.Index, snapSuffix)
77 b := pbutil.MustMarshal(snapshot)
78 crc := crc32.Update(0, crcTable, b)
79 snap := snappb.Snapshot{Crc: crc, Data: b}
80 d, err := snap.Marshal()
81 if err != nil {
82 return err
83 } else {
84 marshallingDurations.Observe(float64(time.Since(start)) / float64(time.Second))
85 }
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 names, err := s.snapNames()
101 if err != nil {
102 return nil, err
103 }
104 var snap *raftpb.Snapshot
105 for _, name := range names {
106 if snap, err = loadSnap(s.dir, name); err == nil {
107 break
108 }
109 }
110 if err != nil {
111 return nil, ErrNoSnapshot
112 }
113 return snap, nil
114}
115
116func loadSnap(dir, name string) (*raftpb.Snapshot, error) {
117 fpath := filepath.Join(dir, name)
118 snap, err := Read(fpath)
119 if err != nil {
120 renameBroken(fpath)
121 }
122 return snap, err
123}
124
125// Read reads the snapshot named by snapname and returns the snapshot.
126func Read(snapname string) (*raftpb.Snapshot, error) {
127 b, err := ioutil.ReadFile(snapname)
128 if err != nil {
129 plog.Errorf("cannot read file %v: %v", snapname, err)
130 return nil, err
131 }
132
133 if len(b) == 0 {
134 plog.Errorf("unexpected empty snapshot")
135 return nil, ErrEmptySnapshot
136 }
137
138 var serializedSnap snappb.Snapshot
139 if err = serializedSnap.Unmarshal(b); err != nil {
140 plog.Errorf("corrupted snapshot file %v: %v", snapname, err)
141 return nil, err
142 }
143
144 if len(serializedSnap.Data) == 0 || serializedSnap.Crc == 0 {
145 plog.Errorf("unexpected empty snapshot")
146 return nil, ErrEmptySnapshot
147 }
148
149 crc := crc32.Update(0, crcTable, serializedSnap.Data)
150 if crc != serializedSnap.Crc {
151 plog.Errorf("corrupted snapshot file %v: crc mismatch", snapname)
152 return nil, ErrCRCMismatch
153 }
154
155 var snap raftpb.Snapshot
156 if err = snap.Unmarshal(serializedSnap.Data); err != nil {
157 plog.Errorf("corrupted snapshot file %v: %v", snapname, err)
158 return nil, err
159 }
160 return &snap, nil
161}
162
163// snapNames returns the filename of the snapshots in logical time order (from newest to oldest).
164// If there is no available snapshots, an ErrNoSnapshot will be returned.
165func (s *Snapshotter) snapNames() ([]string, error) {
166 dir, err := os.Open(s.dir)
167 if err != nil {
168 return nil, err
169 }
170 defer dir.Close()
171 names, err := dir.Readdirnames(-1)
172 if err != nil {
173 return nil, err
174 }
175 snaps := checkSuffix(names)
176 if len(snaps) == 0 {
177 return nil, ErrNoSnapshot
178 }
179 sort.Sort(sort.Reverse(sort.StringSlice(snaps)))
180 return snaps, nil
181}
182
183func checkSuffix(names []string) []string {
184 snaps := []string{}
185 for i := range names {
186 if strings.HasSuffix(names[i], snapSuffix) {
187 snaps = append(snaps, names[i])
188 } else {
189 // If we find a file which is not a snapshot then check if it's
190 // a vaild file. If not throw out a warning.
191 if _, ok := validFiles[names[i]]; !ok {
192 plog.Warningf("skipped unexpected non snapshot file %v", names[i])
193 }
194 }
195 }
196 return snaps
197}
198
199func renameBroken(path string) {
200 brokenPath := path + ".broken"
201 if err := os.Rename(path, brokenPath); err != nil {
202 plog.Warningf("cannot rename broken snapshot file %v to %v: %v", path, brokenPath, err)
203 }
204}