642 lines
16 KiB
Go
642 lines
16 KiB
Go
package influxdb
|
|
|
|
import (
|
|
"archive/tar"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/boltdb/bolt"
|
|
)
|
|
|
|
// manifestName is the name of the manifest file in the snapshot.
|
|
const manifestName = "manifest"
|
|
|
|
// Snapshot represents the state of the Server at a given time.
|
|
type Snapshot struct {
|
|
Files []SnapshotFile `json:"files"`
|
|
}
|
|
|
|
// Index returns the highest index across all files.
|
|
func (s *Snapshot) Index() uint64 {
|
|
var index uint64
|
|
for _, f := range s.Files {
|
|
if f.Index > index {
|
|
index = f.Index
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
// Diff returns a Snapshot of files that are newer in s than other.
|
|
func (s *Snapshot) Diff(other *Snapshot) *Snapshot {
|
|
diff := &Snapshot{}
|
|
|
|
// Find versions of files that are newer in s.
|
|
loop:
|
|
for _, a := range s.Files {
|
|
// Try to find a newer version of the file in other.
|
|
// If found then don't append this file and move to the next file.
|
|
for _, b := range other.Files {
|
|
if a.Name != b.Name {
|
|
continue
|
|
} else if a.Index <= b.Index {
|
|
continue loop
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Append the newest version.
|
|
diff.Files = append(diff.Files, a)
|
|
}
|
|
|
|
// Sort files.
|
|
sort.Sort(SnapshotFiles(diff.Files))
|
|
|
|
return diff
|
|
}
|
|
|
|
// Merge returns a Snapshot that combines s with other.
|
|
// Only the newest file between the two snapshots is returned.
|
|
func (s *Snapshot) Merge(other *Snapshot) *Snapshot {
|
|
ret := &Snapshot{}
|
|
ret.Files = make([]SnapshotFile, len(s.Files))
|
|
copy(ret.Files, s.Files)
|
|
|
|
// Update/insert versions of files that are newer in other.
|
|
loop:
|
|
for _, a := range other.Files {
|
|
for i, b := range ret.Files {
|
|
// Ignore if it doesn't match.
|
|
if a.Name != b.Name {
|
|
continue
|
|
}
|
|
|
|
// Update if it's newer and then start the next file.
|
|
if a.Index > b.Index {
|
|
ret.Files[i] = a
|
|
}
|
|
continue loop
|
|
}
|
|
|
|
// If the file wasn't found then append it.
|
|
ret.Files = append(ret.Files, a)
|
|
}
|
|
|
|
// Sort files.
|
|
sort.Sort(SnapshotFiles(ret.Files))
|
|
|
|
return ret
|
|
}
|
|
|
|
// SnapshotFile represents a single file in a Snapshot.
|
|
type SnapshotFile struct {
|
|
Name string `json:"name"` // filename
|
|
Size int64 `json:"size"` // file size
|
|
Index uint64 `json:"index"` // highest index applied
|
|
}
|
|
|
|
// SnapshotFiles represents a sortable list of snapshot files.
|
|
type SnapshotFiles []SnapshotFile
|
|
|
|
func (p SnapshotFiles) Len() int { return len(p) }
|
|
func (p SnapshotFiles) Less(i, j int) bool { return p[i].Name < p[j].Name }
|
|
func (p SnapshotFiles) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
// SnapshotReader reads a snapshot from a Reader.
|
|
// This type is not safe for concurrent use.
|
|
type SnapshotReader struct {
|
|
tr *tar.Reader
|
|
snapshot *Snapshot
|
|
}
|
|
|
|
// NewSnapshotReader returns a new SnapshotReader reading from r.
|
|
func NewSnapshotReader(r io.Reader) *SnapshotReader {
|
|
return &SnapshotReader{
|
|
tr: tar.NewReader(r),
|
|
}
|
|
}
|
|
|
|
// Snapshot returns the snapshot meta data.
|
|
func (sr *SnapshotReader) Snapshot() (*Snapshot, error) {
|
|
if err := sr.readSnapshot(); err != nil {
|
|
return nil, err
|
|
}
|
|
return sr.snapshot, nil
|
|
}
|
|
|
|
// readSnapshot reads the first entry from the snapshot and materializes the snapshot.
|
|
// This is skipped if the snapshot manifest has already been read.
|
|
func (sr *SnapshotReader) readSnapshot() error {
|
|
// Already read, ignore.
|
|
if sr.snapshot != nil {
|
|
return nil
|
|
}
|
|
|
|
// Read manifest header.
|
|
hdr, err := sr.tr.Next()
|
|
if err != nil {
|
|
return fmt.Errorf("snapshot header: %s", err)
|
|
} else if hdr.Name != manifestName {
|
|
return fmt.Errorf("invalid snapshot header: expected manifest")
|
|
}
|
|
|
|
// Materialize snapshot.
|
|
var snapshot Snapshot
|
|
if err := json.NewDecoder(sr.tr).Decode(&snapshot); err != nil {
|
|
return fmt.Errorf("decode manifest: %s", err)
|
|
}
|
|
sr.snapshot = &snapshot
|
|
|
|
return nil
|
|
}
|
|
|
|
// Next returns the next file in the snapshot.
|
|
func (sr *SnapshotReader) Next() (SnapshotFile, error) {
|
|
// Read snapshot if it hasn't been read yet.
|
|
if err := sr.readSnapshot(); err != nil {
|
|
return SnapshotFile{}, err
|
|
}
|
|
|
|
// Read next header.
|
|
hdr, err := sr.tr.Next()
|
|
if err != nil {
|
|
return SnapshotFile{}, err
|
|
}
|
|
|
|
// Match header to file in snapshot.
|
|
for i := range sr.snapshot.Files {
|
|
if sr.snapshot.Files[i].Name == hdr.Name {
|
|
return sr.snapshot.Files[i], nil
|
|
}
|
|
}
|
|
|
|
// Return error if file is not in the snapshot.
|
|
return SnapshotFile{}, fmt.Errorf("snapshot entry not found in manifest: %s", hdr.Name)
|
|
}
|
|
|
|
// Read reads the current entry in the snapshot.
|
|
func (sr *SnapshotReader) Read(b []byte) (n int, err error) {
|
|
// Read snapshot if it hasn't been read yet.
|
|
if err := sr.readSnapshot(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Pass read through to the tar reader.
|
|
return sr.tr.Read(b)
|
|
}
|
|
|
|
// SnapshotsReader reads from a collection of snapshots.
|
|
// Only files with the highest index are read from the reader.
|
|
// This type is not safe for concurrent use.
|
|
type SnapshotsReader struct {
|
|
readers []*SnapshotReader // underlying snapshot readers
|
|
files []*SnapshotFile // current file for each reader
|
|
|
|
snapshot *Snapshot // combined snapshot from all readers
|
|
index int // index of file in snapshot to read
|
|
curr *SnapshotReader // current reader
|
|
}
|
|
|
|
// NewSnapshotsReader returns a new SnapshotsReader reading from a list of readers.
|
|
func NewSnapshotsReader(readers ...io.Reader) *SnapshotsReader {
|
|
r := &SnapshotsReader{
|
|
readers: make([]*SnapshotReader, len(readers)),
|
|
files: make([]*SnapshotFile, len(readers)),
|
|
index: -1,
|
|
}
|
|
for i := range readers {
|
|
r.readers[i] = NewSnapshotReader(readers[i])
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Snapshot returns the combined snapshot from all readers.
|
|
func (ssr *SnapshotsReader) Snapshot() (*Snapshot, error) {
|
|
// Use snapshot if it's already been calculated.
|
|
if ssr.snapshot != nil {
|
|
return ssr.snapshot, nil
|
|
}
|
|
|
|
// Build snapshot from other readers.
|
|
ss := &Snapshot{}
|
|
for i, sr := range ssr.readers {
|
|
other, err := sr.Snapshot()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("snapshot: idx=%d, err=%s", i, err)
|
|
}
|
|
ss = ss.Merge(other)
|
|
}
|
|
|
|
// Cache snapshot and return.
|
|
ssr.snapshot = ss
|
|
return ss, nil
|
|
}
|
|
|
|
// Next returns the next file in the reader.
|
|
func (ssr *SnapshotsReader) Next() (SnapshotFile, error) {
|
|
ss, err := ssr.Snapshot()
|
|
if err != nil {
|
|
return SnapshotFile{}, fmt.Errorf("snapshot: %s", err)
|
|
}
|
|
|
|
// Return EOF if there are no more files in snapshot.
|
|
if ssr.index == len(ss.Files)-1 {
|
|
ssr.curr = nil
|
|
return SnapshotFile{}, io.EOF
|
|
}
|
|
|
|
// Queue up next files.
|
|
if err := ssr.nextFiles(); err != nil {
|
|
return SnapshotFile{}, fmt.Errorf("next files: %s", err)
|
|
}
|
|
|
|
// Increment the file index.
|
|
ssr.index++
|
|
sf := ss.Files[ssr.index]
|
|
|
|
// Find the matching reader. Clear other readers.
|
|
var sr *SnapshotReader
|
|
for i, f := range ssr.files {
|
|
if f == nil || f.Name != sf.Name {
|
|
continue
|
|
}
|
|
|
|
// Set reader to the first match.
|
|
if sr == nil && *f == sf {
|
|
sr = ssr.readers[i]
|
|
}
|
|
ssr.files[i] = nil
|
|
}
|
|
|
|
// Return an error if file doesn't match.
|
|
// This shouldn't happen unless the underlying snapshot is altered.
|
|
if sr == nil {
|
|
return SnapshotFile{}, fmt.Errorf("snaphot file not found in readers: %s", sf.Name)
|
|
}
|
|
|
|
// Set current reader.
|
|
ssr.curr = sr
|
|
|
|
// Return file.
|
|
return sf, nil
|
|
}
|
|
|
|
// nextFiles queues up a next file for all readers.
|
|
func (ssr *SnapshotsReader) nextFiles() error {
|
|
for i, sr := range ssr.readers {
|
|
if ssr.files[i] == nil {
|
|
// Read next file.
|
|
sf, err := sr.Next()
|
|
if err == io.EOF {
|
|
ssr.files[i] = nil
|
|
continue
|
|
} else if err != nil {
|
|
return fmt.Errorf("next: reader=%d, err=%s", i, err)
|
|
}
|
|
|
|
// Cache file.
|
|
ssr.files[i] = &sf
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// nextIndex returns the index of the next reader to read from.
|
|
// Returns -1 if all readers are at EOF.
|
|
func (ssr *SnapshotsReader) nextIndex() int {
|
|
// Find the next file by name and lowest index.
|
|
index := -1
|
|
for i, f := range ssr.files {
|
|
if f == nil {
|
|
continue
|
|
} else if index == -1 {
|
|
index = i
|
|
} else if f.Name < ssr.files[index].Name {
|
|
index = i
|
|
} else if f.Name == ssr.files[index].Name && f.Index > ssr.files[index].Index {
|
|
index = i
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
// Read reads the current entry in the reader.
|
|
func (ssr *SnapshotsReader) Read(b []byte) (n int, err error) {
|
|
if ssr.curr == nil {
|
|
return 0, io.EOF
|
|
}
|
|
return ssr.curr.Read(b)
|
|
}
|
|
|
|
// OpenFileSnapshotsReader returns a SnapshotsReader based on the path of the base snapshot.
|
|
// Returns the underlying files which need to be closed separately.
|
|
func OpenFileSnapshotsReader(path string) (*SnapshotsReader, []io.Closer, error) {
|
|
var readers []io.Reader
|
|
var closers []io.Closer
|
|
if err := func() error {
|
|
// Open original snapshot file.
|
|
f, err := os.Open(path)
|
|
if os.IsNotExist(err) {
|
|
return err
|
|
} else if err != nil {
|
|
return fmt.Errorf("open snapshot: %s", err)
|
|
}
|
|
readers = append(readers, f)
|
|
closers = append(closers, f)
|
|
|
|
// Open all incremental snapshots.
|
|
for i := 0; ; i++ {
|
|
filename := path + fmt.Sprintf(".%d", i)
|
|
f, err := os.Open(filename)
|
|
if os.IsNotExist(err) {
|
|
break
|
|
} else if err != nil {
|
|
return fmt.Errorf("open incremental snapshot: file=%s, err=%s", filename, err)
|
|
}
|
|
readers = append(readers, f)
|
|
closers = append(closers, f)
|
|
}
|
|
|
|
return nil
|
|
}(); err != nil {
|
|
closeAll(closers)
|
|
return nil, nil, err
|
|
}
|
|
|
|
return NewSnapshotsReader(readers...), nil, nil
|
|
}
|
|
|
|
// ReadFileSnapshot returns a Snapshot for a given base snapshot path.
|
|
// This snapshot merges all incremental backup snapshots as well.
|
|
func ReadFileSnapshot(path string) (*Snapshot, error) {
|
|
// Open a multi-snapshot reader.
|
|
ssr, files, err := OpenFileSnapshotsReader(path)
|
|
if os.IsNotExist(err) {
|
|
return nil, err
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("open file snapshots reader: %s", err)
|
|
}
|
|
defer closeAll(files)
|
|
|
|
// Read snapshot.
|
|
ss, err := ssr.Snapshot()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("snapshot: %s", err)
|
|
}
|
|
|
|
return ss, nil
|
|
}
|
|
|
|
func closeAll(a []io.Closer) {
|
|
for _, c := range a {
|
|
_ = c.Close()
|
|
}
|
|
}
|
|
|
|
// SnapshotWriter writes a snapshot and the underlying files to disk as a tar archive.
|
|
type SnapshotWriter struct {
|
|
// The snapshot to write from.
|
|
// Removing files from the snapshot after creation will cause those files to be ignored.
|
|
Snapshot *Snapshot
|
|
|
|
// Writers for each file by filename.
|
|
// Writers will be closed as they're processed and will close by the end of WriteTo().
|
|
FileWriters map[string]SnapshotFileWriter
|
|
}
|
|
|
|
// NewSnapshotWriter returns a new instance of SnapshotWriter.
|
|
func NewSnapshotWriter() *SnapshotWriter {
|
|
return &SnapshotWriter{
|
|
Snapshot: &Snapshot{},
|
|
FileWriters: make(map[string]SnapshotFileWriter),
|
|
}
|
|
}
|
|
|
|
// Close closes all file writers on the snapshot.
|
|
func (sw *SnapshotWriter) Close() error {
|
|
for _, fw := range sw.FileWriters {
|
|
_ = fw.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// closeUnusedWriters closes all file writers not on the snapshot.
|
|
// This allows transactions on these files to be short lived.
|
|
func (sw *SnapshotWriter) closeUnusedWriters() {
|
|
loop:
|
|
for name, fw := range sw.FileWriters {
|
|
// Find writer in snapshot.
|
|
for _, f := range sw.Snapshot.Files {
|
|
if f.Name == name {
|
|
continue loop
|
|
}
|
|
}
|
|
|
|
// If not found then close it.
|
|
_ = fw.Close()
|
|
}
|
|
}
|
|
|
|
// WriteTo writes the snapshot to the writer.
|
|
// File writers are closed as they are written.
|
|
// This function will always return n == 0.
|
|
func (sw *SnapshotWriter) WriteTo(w io.Writer) (n int64, err error) {
|
|
// Close any file writers that aren't required.
|
|
sw.closeUnusedWriters()
|
|
|
|
// Sort snapshot files.
|
|
// This is required for combining multiple snapshots together.
|
|
sort.Sort(SnapshotFiles(sw.Snapshot.Files))
|
|
|
|
// Begin writing a tar file to the output.
|
|
tw := tar.NewWriter(w)
|
|
defer tw.Close()
|
|
|
|
// Write manifest file.
|
|
if err := sw.writeManifestTo(tw); err != nil {
|
|
return 0, fmt.Errorf("write manifest: %s", err)
|
|
}
|
|
|
|
// Write each backup file.
|
|
for _, f := range sw.Snapshot.Files {
|
|
if err := sw.writeFileTo(tw, &f); err != nil {
|
|
return 0, fmt.Errorf("write file: %s", err)
|
|
}
|
|
}
|
|
|
|
// Close tar writer and check error.
|
|
if err := tw.Close(); err != nil {
|
|
return 0, fmt.Errorf("tar close: %s", err)
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// writeManifestTo writes a manifest of the contents of the snapshot to the archive.
|
|
func (sw *SnapshotWriter) writeManifestTo(tw *tar.Writer) error {
|
|
// Convert snapshot to JSON.
|
|
b, err := json.Marshal(sw.Snapshot)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal json: %s", err)
|
|
}
|
|
|
|
// Write header & file.
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: manifestName,
|
|
Size: int64(len(b)),
|
|
Mode: 0666,
|
|
ModTime: time.Now(),
|
|
}); err != nil {
|
|
return fmt.Errorf("write header: %s", err)
|
|
}
|
|
if _, err := tw.Write(b); err != nil {
|
|
return fmt.Errorf("write: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeFileTo writes a single file to the archive.
|
|
func (sw *SnapshotWriter) writeFileTo(tw *tar.Writer, f *SnapshotFile) error {
|
|
// Retrieve the file writer by filename.
|
|
fw := sw.FileWriters[f.Name]
|
|
if fw == nil {
|
|
return fmt.Errorf("file writer not found: name=%s", f.Name)
|
|
}
|
|
|
|
// Write file header.
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: f.Name,
|
|
Size: f.Size,
|
|
Mode: 0666,
|
|
ModTime: time.Now(),
|
|
}); err != nil {
|
|
return fmt.Errorf("write header: file=%s, err=%s", f.Name, err)
|
|
}
|
|
|
|
// Copy the database to the writer.
|
|
if nn, err := fw.WriteTo(tw); err != nil {
|
|
return fmt.Errorf("write: file=%s, err=%s", f.Name, err)
|
|
} else if nn != f.Size {
|
|
return fmt.Errorf("short write: file=%s", f.Name)
|
|
}
|
|
|
|
// Close the writer.
|
|
if err := fw.Close(); err != nil {
|
|
return fmt.Errorf("close: file=%s, err=%s", f.Name, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createServerSnapshotWriter creates a snapshot writer from a locked server.
|
|
func createServerSnapshotWriter(s *Server) (*SnapshotWriter, error) {
|
|
// Exit if the server is closed.
|
|
if !s.opened() {
|
|
return nil, ErrServerClosed
|
|
}
|
|
|
|
// Create snapshot writer.
|
|
sw := NewSnapshotWriter()
|
|
|
|
if err := func() error {
|
|
f, fw, err := createMetaSnapshotFile(s.meta)
|
|
if err != nil {
|
|
return fmt.Errorf("create meta snapshot file: %s", err)
|
|
}
|
|
sw.Snapshot.Files = append(sw.Snapshot.Files, *f)
|
|
sw.FileWriters[f.Name] = fw
|
|
|
|
// Create files for each shard.
|
|
for _, sh := range s.shards {
|
|
f, fw, err := createShardSnapshotFile(sh)
|
|
if err != nil {
|
|
return fmt.Errorf("create meta snapshot file: id=%d, err=%s", sh.ID, err)
|
|
} else if f != nil {
|
|
sw.Snapshot.Files = append(sw.Snapshot.Files, *f)
|
|
sw.FileWriters[f.Name] = fw
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}(); err != nil {
|
|
_ = sw.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return sw, nil
|
|
}
|
|
|
|
func createMetaSnapshotFile(meta *metastore) (*SnapshotFile, SnapshotFileWriter, error) {
|
|
// Begin transaction.
|
|
tx, err := meta.db.Begin(false)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("begin: %s", err)
|
|
}
|
|
|
|
// Create and return file and writer.
|
|
f := &SnapshotFile{
|
|
Name: "meta",
|
|
Size: tx.Size(),
|
|
Index: (&metatx{tx}).index(),
|
|
}
|
|
return f, &boltTxCloser{tx}, nil
|
|
}
|
|
|
|
func createShardSnapshotFile(sh *Shard) (*SnapshotFile, SnapshotFileWriter, error) {
|
|
// Ignore shard if it's not owned by the server.
|
|
if sh.store == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Begin transaction.
|
|
tx, err := sh.store.Begin(false)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("begin: %s", err)
|
|
}
|
|
|
|
// Create and return file and writer.
|
|
f := &SnapshotFile{
|
|
Name: path.Join("shards", filepath.Base(sh.store.Path())),
|
|
Size: tx.Size(),
|
|
Index: shardMetaIndex(tx),
|
|
}
|
|
return f, &boltTxCloser{tx}, nil
|
|
}
|
|
|
|
// SnapshotFileWriter is the interface used for writing a file to a snapshot.
|
|
type SnapshotFileWriter interface {
|
|
io.WriterTo
|
|
io.Closer
|
|
}
|
|
|
|
// boltTxCloser wraps a Bolt transaction to implement io.Closer.
|
|
type boltTxCloser struct {
|
|
*bolt.Tx
|
|
}
|
|
|
|
// Close rollsback the transaction.
|
|
func (tx *boltTxCloser) Close() error { return tx.Rollback() }
|
|
|
|
// NopWriteToCloser returns an io.WriterTo that implements io.Closer.
|
|
func NopWriteToCloser(w io.WriterTo) interface {
|
|
io.WriterTo
|
|
io.Closer
|
|
} {
|
|
return &nopWriteToCloser{w}
|
|
}
|
|
|
|
type nopWriteToCloser struct {
|
|
io.WriterTo
|
|
}
|
|
|
|
func (w *nopWriteToCloser) Close() error { return nil }
|