Add 'validate' command

This commit is contained in:
Ben Johnson
2020-12-31 10:36:48 -07:00
parent cff778464e
commit 3b9275488d
6 changed files with 364 additions and 27 deletions

View File

@@ -7,6 +7,7 @@ import (
"io"
"io/ioutil"
"log"
"math"
"os"
"path/filepath"
"sort"
@@ -30,7 +31,10 @@ type Replica interface {
Stop()
// Returns the last replication position.
Pos() Pos
LastPos() Pos
// Returns the computed position of the replica for a given generation.
CalcPos(generation string) (Pos, error)
// Returns a list of generation names for the replica.
Generations(ctx context.Context) ([]string, error)
@@ -51,7 +55,7 @@ type Replica interface {
// Returns the highest index for a WAL file that occurs before timestamp.
// If timestamp is zero, returns the highest WAL index.
WALIndexAt(ctx context.Context, generation string, timestamp time.Time) (int, error)
WALIndexAt(ctx context.Context, generation string, maxIndex int, timestamp time.Time) (int, error)
// Returns a reader for snapshot data at the given generation/index.
SnapshotReader(ctx context.Context, generation string, index int) (io.ReadCloser, error)
@@ -110,8 +114,8 @@ func (r *FileReplica) Type() string {
return "file"
}
// Pos returns the last successfully replicated position.
func (r *FileReplica) Pos() Pos {
// LastPos returns the last successfully replicated position.
func (r *FileReplica) LastPos() Pos {
r.mu.RLock()
defer r.mu.RUnlock()
return r.pos
@@ -449,9 +453,9 @@ func (r *FileReplica) monitor(ctx context.Context) {
}
}
// calcPos returns the position for the replica for the current generation.
// CalcPos returns the position for the replica for the current generation.
// Returns a zero value if there is no active generation.
func (r *FileReplica) calcPos(generation string) (pos Pos, err error) {
func (r *FileReplica) CalcPos(generation string) (pos Pos, err error) {
pos.Generation = generation
// Find maximum snapshot index.
@@ -561,8 +565,8 @@ func (r *FileReplica) Sync(ctx context.Context) (err error) {
}
// Determine position, if necessary.
if r.Pos().IsZero() {
pos, err := r.calcPos(generation)
if r.LastPos().IsZero() {
pos, err := r.CalcPos(generation)
if err != nil {
return fmt.Errorf("cannot determine replica position: %s", err)
}
@@ -592,7 +596,7 @@ func (r *FileReplica) Sync(ctx context.Context) (err error) {
}
func (r *FileReplica) syncWAL(ctx context.Context) (err error) {
rd, err := r.db.ShadowWALReader(r.Pos())
rd, err := r.db.ShadowWALReader(r.LastPos())
if err == io.EOF {
return err
} else if err != nil {
@@ -697,9 +701,9 @@ func (r *FileReplica) SnapshotIndexAt(ctx context.Context, generation string, ti
return index, nil
}
// Returns the highest index for a WAL file that occurs before timestamp.
// Returns the highest index for a WAL file that occurs before maxIndex & timestamp.
// If timestamp is zero, returns the highest WAL index.
func (r *FileReplica) WALIndexAt(ctx context.Context, generation string, timestamp time.Time) (int, error) {
func (r *FileReplica) WALIndexAt(ctx context.Context, generation string, maxIndex int, timestamp time.Time) (int, error) {
names, err := r.WALSubdirNames(generation)
if err != nil {
return 0, err
@@ -724,6 +728,8 @@ func (r *FileReplica) WALIndexAt(ctx context.Context, generation string, timesta
continue // not a snapshot, skip
} else if !timestamp.IsZero() && fi.ModTime().After(timestamp) {
continue // after timestamp, skip
} else if idx > maxIndex {
continue // after timestamp, skip
} else if idx < index {
continue // earlier index, skip
}
@@ -732,6 +738,11 @@ func (r *FileReplica) WALIndexAt(ctx context.Context, generation string, timesta
}
}
// If max index is specified but not found, return an error.
if maxIndex != math.MaxInt64 && index != maxIndex {
return index, fmt.Errorf("unable to locate index %d in generation %q, highest index was %d", maxIndex, generation, index)
}
return index, nil
}