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  }