github.com/grafana/pyroscope@v1.18.0/pkg/metastore/raftnode/snapshot.go (about) 1 package raftnode 2 3 import ( 4 "fmt" 5 "io" 6 "io/fs" 7 "os" 8 "path/filepath" 9 10 "github.com/go-kit/log/level" 11 ) 12 13 // importSnapshots moves snapshots from SnapshotsImportDir to SnapshotsDir. 14 // 15 // When initializing the snapshot store, Raft creates a "snapshots" subdirectory. 16 // To maintain consistency, this behavior is replicated here – so "snapshots" should 17 // not be included in the configured path. 18 // 19 // Note: the import process does not guarantee atomicity, as it may involve moving 20 // files across different file systems. If an error occurs during the import, 21 // both the source and destination directories may be left in an inconsistent state. 22 // The function does not attempt to recover from such a state, but it will try to 23 // continue copying on the next call. 24 func (n *Node) importSnapshots() error { 25 if n.config.SnapshotsImportDir == "" { 26 return nil 27 } 28 29 importDir := filepath.Join(n.config.SnapshotsImportDir, "snapshots") 30 snapshotsDir := filepath.Join(n.config.SnapshotsDir, "snapshots") 31 32 level.Info(n.logger).Log("msg", "importing snapshots") 33 entries, err := fs.ReadDir(os.DirFS(importDir), ".") 34 if err != nil { 35 return fmt.Errorf("failed to read dir: %w", err) 36 } 37 38 for _, entry := range entries { 39 if !entry.IsDir() { 40 continue 41 } 42 dstDir := filepath.Join(snapshotsDir, entry.Name()) 43 srcDir := filepath.Join(importDir, entry.Name()) 44 if err = n.copySnapshot(srcDir, dstDir); err != nil { 45 return fmt.Errorf("failed to import snapshot %q: %w", entry.Name(), err) 46 } 47 } 48 49 return nil 50 } 51 52 func (n *Node) copySnapshot(srcDir, dstDir string) error { 53 entries, err := fs.ReadDir(os.DirFS(srcDir), ".") 54 if err != nil { 55 return fmt.Errorf("failed to read snapshot dir: %w", err) 56 } 57 var hasFiles bool 58 for _, entry := range entries { 59 if entry.IsDir() { 60 continue 61 } 62 hasFiles = true 63 break 64 } 65 if !hasFiles { 66 return nil 67 } 68 69 level.Info(n.logger).Log("msg", "importing snapshot", "snapshot", srcDir) 70 if err = os.MkdirAll(dstDir, 0755); err != nil { 71 return fmt.Errorf("failed to create snapshot directory: %w", err) 72 } 73 74 for _, entry := range entries { 75 srcPath := filepath.Join(srcDir, entry.Name()) 76 dstPath := filepath.Join(dstDir, entry.Name()) 77 if err = copyFile(srcPath, dstPath); err != nil { 78 return fmt.Errorf("failed to copy snapshot file %q: %w", entry.Name(), err) 79 } 80 if err = os.Remove(srcPath); err != nil { 81 level.Warn(n.logger).Log("msg", "failed to remove source file after copy", "file", srcPath, "err", err) 82 } 83 } 84 85 return nil 86 } 87 88 func copyFile(src, dst string) error { 89 sourceFile, err := os.Open(src) 90 if err != nil { 91 return fmt.Errorf("failed to open source file: %w", err) 92 } 93 defer func() { 94 _ = sourceFile.Close() 95 }() 96 sourceInfo, err := sourceFile.Stat() 97 if err != nil { 98 return fmt.Errorf("failed to stat source file: %w", err) 99 } 100 destFile, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, sourceInfo.Mode()) 101 if err != nil { 102 return fmt.Errorf("failed to create destination file: %w", err) 103 } 104 if _, err = io.Copy(destFile, sourceFile); err != nil { 105 _ = destFile.Close() 106 return fmt.Errorf("failed to copy file contents: %w", err) 107 } 108 return destFile.Close() 109 }