channeldb/db: adds optional dry-run abort for migrations

This commit is contained in:
Conner Fromknecht 2020-05-11 15:38:45 -07:00
parent f3a427724b
commit c775819372
No known key found for this signature in database
GPG Key ID: E7D737B67FA592C7
4 changed files with 87 additions and 4 deletions

@ -25,6 +25,12 @@ const (
dbFilePermission = 0600
)
var (
// ErrDryRunMigrationOK signals that a migration executed successful,
// but we intentionally did not commit the result.
ErrDryRunMigrationOK = errors.New("Dry run migration successful")
)
// migration is a function which takes a prior outdated version of the database
// instances and mutates the key/bucket structure to arrive at a more
// up-to-date version of the database.
@ -145,6 +151,7 @@ type DB struct {
dbPath string
graph *ChannelGraph
clock clock.Clock
dryRun bool
}
// Open opens an existing channeldb. Any necessary schemas migrations due to
@ -174,6 +181,7 @@ func Open(dbPath string, modifiers ...OptionModifier) (*DB, error) {
Backend: bdb,
dbPath: dbPath,
clock: opts.clock,
dryRun: opts.dryRun,
}
chanDB.graph = newChannelGraph(
chanDB, opts.RejectCacheSize, opts.ChannelCacheSize,
@ -1227,7 +1235,18 @@ func (d *DB) syncVersions(versions []version) error {
}
meta.DbVersionNumber = latestVersion
return putMeta(meta, tx)
err := putMeta(meta, tx)
if err != nil {
return err
}
// In dry-run mode, return an error to prevent the transaction
// from committing.
if d.dryRun {
return ErrDryRunMigrationOK
}
return nil
})
}

@ -45,6 +45,16 @@ func TestOpenWithCreate(t *testing.T) {
if !fileExists(dbPath) {
t.Fatalf("channeldb failed to create data directory")
}
// Now, reopen the same db in dry run migration mode. Since we have not
// applied any migrations, this should ignore the flag and not fail.
cdb, err = Open(dbPath, OptionDryRunMigration(true))
if err != nil {
t.Fatalf("unable to create channeldb: %v", err)
}
if err := cdb.Close(); err != nil {
t.Fatalf("unable to close channeldb: %v", err)
}
}
// TestWipe tests that the database wipe operation completes successfully

@ -12,13 +12,14 @@ import (
// applyMigration is a helper test function that encapsulates the general steps
// which are needed to properly check the result of applying migration function.
func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB),
migrationFunc migration, shouldFail bool) {
migrationFunc migration, shouldFail bool, dryRun bool) {
cdb, cleanUp, err := makeTestDB()
defer cleanUp()
if err != nil {
t.Fatal(err)
}
cdb.dryRun = dryRun
// Create a test node that will be our source node.
testNode, err := createTestVertex(cdb)
@ -54,6 +55,9 @@ func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB),
defer func() {
if r := recover(); r != nil {
if dryRun && r != ErrDryRunMigrationOK {
t.Fatalf("expected dry run migration OK")
}
err = errors.New(r)
}
@ -256,7 +260,8 @@ func TestMigrationWithPanic(t *testing.T) {
beforeMigrationFunc,
afterMigrationFunc,
migrationWithPanic,
true)
true,
false)
}
// TestMigrationWithFatal asserts that migrations which fail do not modify the
@ -330,7 +335,8 @@ func TestMigrationWithFatal(t *testing.T) {
beforeMigrationFunc,
afterMigrationFunc,
migrationWithFatal,
true)
true,
false)
}
// TestMigrationWithoutErrors asserts that a successful migration has its
@ -404,6 +410,7 @@ func TestMigrationWithoutErrors(t *testing.T) {
beforeMigrationFunc,
afterMigrationFunc,
migrationWithoutErrors,
false,
false)
}
@ -446,3 +453,38 @@ func TestMigrationReversion(t *testing.T) {
"want: %v, got: %v", ErrDBReversion, err)
}
}
// TestMigrationDryRun ensures that opening the database in dry run migration
// mode will fail and not commit the migration.
func TestMigrationDryRun(t *testing.T) {
t.Parallel()
// Nothing to do, will inspect version number.
beforeMigrationFunc := func(d *DB) {}
// Check that version of database version is not modified.
afterMigrationFunc := func(d *DB) {
err := kvdb.View(d, func(tx kvdb.ReadTx) error {
meta, err := d.FetchMeta(nil)
if err != nil {
t.Fatal(err)
}
if meta.DbVersionNumber != 0 {
t.Fatal("dry run migration was not aborted")
}
return nil
})
if err != nil {
t.Fatalf("unable to apply after func: %v", err)
}
}
applyMigration(t,
beforeMigrationFunc,
afterMigrationFunc,
func(kvdb.RwTx) error { return nil },
true,
true)
}

@ -31,6 +31,10 @@ type Options struct {
// clock is the time source used by the database.
clock clock.Clock
// dryRun will fail to commit a successful migration when opening the
// database if set to true.
dryRun bool
}
// DefaultOptions returns an Options populated with default values.
@ -73,3 +77,11 @@ func OptionClock(clock clock.Clock) OptionModifier {
o.clock = clock
}
}
// OptionDryRunMigration controls whether or not to intentially fail to commit a
// successful migration that occurs when opening the database.
func OptionDryRunMigration(dryRun bool) OptionModifier {
return func(o *Options) {
o.dryRun = dryRun
}
}