github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/helper/snapshot/snapshot.go (about) 1 // snapshot manages the interactions between Nomad and Raft in order to take 2 // and restore snapshots for disaster recovery. The internal format of a 3 // snapshot is simply a tar file, as described in archive.go. 4 package snapshot 5 6 import ( 7 "compress/gzip" 8 "crypto/sha256" 9 "encoding/base64" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 15 "github.com/hashicorp/go-hclog" 16 "github.com/hashicorp/raft" 17 ) 18 19 // Snapshot is a structure that holds state about a temporary file that is used 20 // to hold a snapshot. By using an intermediate file we avoid holding everything 21 // in memory. 22 type Snapshot struct { 23 file *os.File 24 index uint64 25 checksum string 26 } 27 28 // New takes a state snapshot of the given Raft instance into a temporary file 29 // and returns an object that gives access to the file as an io.Reader. You must 30 // arrange to call Close() on the returned object or else you will leak a 31 // temporary file. 32 func New(logger hclog.Logger, r *raft.Raft) (*Snapshot, error) { 33 // Take the snapshot. 34 future := r.Snapshot() 35 if err := future.Error(); err != nil { 36 return nil, fmt.Errorf("Raft error when taking snapshot: %v", err) 37 } 38 39 // Open up the snapshot. 40 metadata, snap, err := future.Open() 41 if err != nil { 42 return nil, fmt.Errorf("failed to open snapshot: %v:", err) 43 } 44 defer func() { 45 if err := snap.Close(); err != nil { 46 logger.Error("Failed to close Raft snapshot", "error", err) 47 } 48 }() 49 50 // Make a scratch file to receive the contents so that we don't buffer 51 // everything in memory. This gets deleted in Close() since we keep it 52 // around for re-reading. 53 archive, err := ioutil.TempFile("", "snapshot") 54 if err != nil { 55 return nil, fmt.Errorf("failed to create snapshot file: %v", err) 56 } 57 58 // If anything goes wrong after this point, we will attempt to clean up 59 // the temp file. The happy path will disarm this. 60 var keep bool 61 defer func() { 62 if keep { 63 return 64 } 65 66 if err := os.Remove(archive.Name()); err != nil { 67 logger.Error("Failed to clean up temp snapshot", "error", err) 68 } 69 }() 70 71 hash := sha256.New() 72 out := io.MultiWriter(hash, archive) 73 74 // Wrap the file writer in a gzip compressor. 75 compressor := gzip.NewWriter(out) 76 77 // Write the archive. 78 if err := write(compressor, metadata, snap); err != nil { 79 return nil, fmt.Errorf("failed to write snapshot file: %v", err) 80 } 81 82 // Finish the compressed stream. 83 if err := compressor.Close(); err != nil { 84 return nil, fmt.Errorf("failed to compress snapshot file: %v", err) 85 } 86 87 // Sync the compressed file and rewind it so it's ready to be streamed 88 // out by the caller. 89 if err := archive.Sync(); err != nil { 90 return nil, fmt.Errorf("failed to sync snapshot: %v", err) 91 } 92 if _, err := archive.Seek(0, 0); err != nil { 93 return nil, fmt.Errorf("failed to rewind snapshot: %v", err) 94 } 95 96 checksum := "sha-256=" + base64.StdEncoding.EncodeToString(hash.Sum(nil)) 97 98 keep = true 99 return &Snapshot{archive, metadata.Index, checksum}, nil 100 } 101 102 // Index returns the index of the snapshot. This is safe to call on a nil 103 // snapshot, it will just return 0. 104 func (s *Snapshot) Index() uint64 { 105 if s == nil { 106 return 0 107 } 108 return s.index 109 } 110 111 func (s *Snapshot) Checksum() string { 112 if s == nil { 113 return "" 114 } 115 return s.checksum 116 } 117 118 // Read passes through to the underlying snapshot file. This is safe to call on 119 // a nil snapshot, it will just return an EOF. 120 func (s *Snapshot) Read(p []byte) (n int, err error) { 121 if s == nil { 122 return 0, io.EOF 123 } 124 return s.file.Read(p) 125 } 126 127 // Close closes the snapshot and removes any temporary storage associated with 128 // it. You must arrange to call this whenever NewSnapshot() has been called 129 // successfully. This is safe to call on a nil snapshot. 130 func (s *Snapshot) Close() error { 131 if s == nil { 132 return nil 133 } 134 135 if err := s.file.Close(); err != nil { 136 return err 137 } 138 return os.Remove(s.file.Name()) 139 } 140 141 // Verify takes the snapshot from the reader and verifies its contents. 142 func Verify(in io.Reader) (*raft.SnapshotMeta, error) { 143 // Wrap the reader in a gzip decompressor. 144 decomp, err := gzip.NewReader(in) 145 if err != nil { 146 return nil, fmt.Errorf("failed to decompress snapshot: %v", err) 147 } 148 defer decomp.Close() 149 150 // Read the archive, throwing away the snapshot data. 151 var metadata raft.SnapshotMeta 152 if err := read(decomp, &metadata, ioutil.Discard); err != nil { 153 return nil, fmt.Errorf("failed to read snapshot file: %v", err) 154 } 155 156 if err := concludeGzipRead(decomp); err != nil { 157 return nil, err 158 } 159 160 return &metadata, nil 161 } 162 163 // concludeGzipRead should be invoked after you think you've consumed all of 164 // the data from the gzip stream. It will error if the stream was corrupt. 165 // 166 // The docs for gzip.Reader say: "Clients should treat data returned by Read as 167 // tentative until they receive the io.EOF marking the end of the data." 168 func concludeGzipRead(decomp *gzip.Reader) error { 169 extra, err := ioutil.ReadAll(decomp) // ReadAll consumes the EOF 170 if err != nil { 171 return err 172 } else if len(extra) != 0 { 173 return fmt.Errorf("%d unread uncompressed bytes remain", len(extra)) 174 } 175 return nil 176 } 177 178 type readWrapper struct { 179 in io.Reader 180 c int 181 } 182 183 func (r *readWrapper) Read(b []byte) (int, error) { 184 n, err := r.in.Read(b) 185 r.c += n 186 if err != nil && err != io.EOF { 187 return n, fmt.Errorf("failed to read after %v: %v", r.c, err) 188 } 189 return n, err 190 } 191 192 // Restore takes the snapshot from the reader and attempts to apply it to the 193 // given Raft instance. 194 func Restore(logger hclog.Logger, in io.Reader, r *raft.Raft) error { 195 // Wrap the reader in a gzip decompressor. 196 decomp, err := gzip.NewReader(&readWrapper{in, 0}) 197 if err != nil { 198 return fmt.Errorf("failed to decompress snapshot: %v", err) 199 } 200 defer func() { 201 if err := decomp.Close(); err != nil { 202 logger.Error("Failed to close snapshot decompressor", "error", err) 203 } 204 }() 205 206 // Make a scratch file to receive the contents of the snapshot data so 207 // we can avoid buffering in memory. 208 snap, err := ioutil.TempFile("", "snapshot") 209 if err != nil { 210 return fmt.Errorf("failed to create temp snapshot file: %v", err) 211 } 212 defer func() { 213 if err := snap.Close(); err != nil { 214 logger.Error("Failed to close temp snapshot", "error", err) 215 } 216 if err := os.Remove(snap.Name()); err != nil { 217 logger.Error("Failed to clean up temp snapshot", "error", err) 218 } 219 }() 220 221 // Read the archive. 222 var metadata raft.SnapshotMeta 223 if err := read(decomp, &metadata, snap); err != nil { 224 return fmt.Errorf("failed to read snapshot file: %v", err) 225 } 226 227 if err := concludeGzipRead(decomp); err != nil { 228 return err 229 } 230 231 // Sync and rewind the file so it's ready to be read again. 232 if err := snap.Sync(); err != nil { 233 return fmt.Errorf("failed to sync temp snapshot: %v", err) 234 } 235 if _, err := snap.Seek(0, 0); err != nil { 236 return fmt.Errorf("failed to rewind temp snapshot: %v", err) 237 } 238 239 // Feed the snapshot into Raft. 240 if err := r.Restore(&metadata, snap, 0); err != nil { 241 return fmt.Errorf("Raft error when restoring snapshot: %v", err) 242 } 243 244 return nil 245 }