You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.0 KiB
Go
204 lines
5.0 KiB
Go
package snapshot2
|
|
|
|
import (
|
|
"expvar"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/raft"
|
|
)
|
|
|
|
const (
|
|
persistSize = "latest_persist_size"
|
|
persistDuration = "latest_persist_duration"
|
|
reap_snapshots_duration = "reap_snapshots_duration"
|
|
numSnapshotsReaped = "num_snapshots_reaped"
|
|
)
|
|
|
|
const (
|
|
metaFileName = "meta.json"
|
|
tmpSuffix = ".tmp"
|
|
)
|
|
|
|
// stats captures stats for the Store.
|
|
var stats *expvar.Map
|
|
|
|
// ResetStats resets the expvar stats for this module. Mostly for test purposes.
|
|
func ResetStats() {
|
|
stats.Init()
|
|
stats.Add(persistSize, 0)
|
|
stats.Add(persistDuration, 0)
|
|
stats.Add(reap_snapshots_duration, 0)
|
|
stats.Add(numSnapshotsReaped, 0)
|
|
}
|
|
|
|
// Store stores Snapshots.
|
|
type Store struct {
|
|
dir string
|
|
}
|
|
|
|
// NewStore returns a new Snapshot Store.
|
|
func NewStore(dir string) (*Store, error) {
|
|
return &Store{dir: dir}, nil
|
|
}
|
|
|
|
// Create creates a new Sink object, ready for writing a snapshot. Sinks make certain assumptions about
|
|
// the state of the store, and if those assumptions were changed by another Sink writing to the store
|
|
// it could cause failures. Therefore we only allow 1 Sink to be in existence at a time. This shouldn't
|
|
// be a problem, since snapshots are taken infrequently in one at a time.
|
|
func (s *Store) Create(version raft.SnapshotVersion, index, term uint64, configuration raft.Configuration,
|
|
configurationIndex uint64, trans raft.Transport) (raft.SnapshotSink, error) {
|
|
|
|
meta := &raft.SnapshotMeta{
|
|
ID: snapshotName(term, index),
|
|
Index: index,
|
|
Term: term,
|
|
Configuration: configuration,
|
|
ConfigurationIndex: configurationIndex,
|
|
Version: version,
|
|
}
|
|
sink := NewSink(s, meta)
|
|
if err := sink.Open(); err != nil {
|
|
return nil, err
|
|
}
|
|
return sink, nil
|
|
}
|
|
|
|
// List returns a list of all the snapshots in the Store.
|
|
func (s *Store) List() ([]*raft.SnapshotMeta, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Open opens the snapshot with the given ID.
|
|
func (s *Store) Open(id string) (*raft.SnapshotMeta, io.ReadCloser, error) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Stats returns stats about the Snapshot Store.
|
|
func (s *Store) Stats() (map[string]interface{}, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Reap reaps old snapshots.
|
|
func (s *Store) Reap() error {
|
|
return nil
|
|
}
|
|
|
|
// FullNeeded returns true if the next type of snapshot needed
|
|
// by the Store is a full snapshot.
|
|
func (s *Store) FullNeeded() bool {
|
|
return true
|
|
}
|
|
|
|
// Dir returns the directory where the snapshots are stored.
|
|
func (s *Store) Dir() string {
|
|
return s.dir
|
|
}
|
|
|
|
// RemoveAllTmpSnapshotData removes all temporary Snapshot data from the store.
|
|
// This process is defined as follows: for every directory in dir, if the directory
|
|
// is a temporary directory, remove the directory. Then remove all other files
|
|
// that contain the name of a temporary directory, minus the temporary suffix,
|
|
// as prefix.
|
|
func RemoveAllTmpSnapshotData(dir string) error {
|
|
// List all directories in the snapshot directory.
|
|
directories, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
for _, d := range directories {
|
|
// If the directory is a temporary directory, remove it.
|
|
if isTmpName(d.Name()) {
|
|
if err := os.RemoveAll(filepath.Join(dir, d.Name())); err != nil {
|
|
return err
|
|
}
|
|
files, err := filepath.Glob(filepath.Join(dir, nonTmpName(d.Name())) + "*")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, f := range files {
|
|
if err := os.Remove(f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getSnapshots returns a list of all snapshots in the store, sorted
|
|
// from oldest to newest.
|
|
func (s *Store) getSnapshots() ([]string, error) {
|
|
directories, err := os.ReadDir(s.dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var snapshots []string
|
|
for _, d := range directories {
|
|
if !isTmpName(d.Name()) {
|
|
snapshots = append(snapshots, d.Name())
|
|
}
|
|
}
|
|
return snapshots, nil
|
|
}
|
|
|
|
// snapshotName generates a name for the snapshot.
|
|
func snapshotName(term, index uint64) string {
|
|
now := time.Now()
|
|
msec := now.UnixNano() / int64(time.Millisecond)
|
|
return fmt.Sprintf("%d-%d-%d", term, index, msec)
|
|
}
|
|
|
|
func tmpName(path string) string {
|
|
return path + tmpSuffix
|
|
}
|
|
|
|
func nonTmpName(path string) string {
|
|
return strings.TrimSuffix(path, tmpSuffix)
|
|
}
|
|
|
|
func isTmpName(name string) bool {
|
|
return filepath.Ext(name) == tmpSuffix
|
|
}
|
|
|
|
func syncDir(dir string) error {
|
|
fh, err := os.Open(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fh.Close()
|
|
return fh.Sync()
|
|
}
|
|
|
|
// syncDirParentMaybe syncsthe given directory, but only on non-Windows platforms.
|
|
func syncDirMaybe(dir string) error {
|
|
if runtime.GOOS == "windows" {
|
|
return nil
|
|
}
|
|
return syncDir(dir)
|
|
}
|
|
|
|
func fileExists(path string) bool {
|
|
_, err := os.Stat(path)
|
|
return !os.IsNotExist(err)
|
|
}
|
|
|
|
// removeAllPrefix removes all files in the given directory that have the given prefix.
|
|
func removeAllPrefix(path, prefix string) error {
|
|
files, err := filepath.Glob(filepath.Join(path, prefix) + "*")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, f := range files {
|
|
if err := os.RemoveAll(f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|