diff --git a/cmd_migrate_db.go b/cmd_migrate_db.go index 9ec30a8..0a18b74 100644 --- a/cmd_migrate_db.go +++ b/cmd_migrate_db.go @@ -99,6 +99,7 @@ type migrateDBCommand struct { ForceNewMigration bool `long:"force-new-migration" description:"Force a new migration from the beginning of the source DB so the resume state will be discarded"` ForceVerifyDB bool `long:"force-verify-db" description:"Force a verification verifies two already marked (tombstoned and already migrated) dbs to make sure that the source db equals the content of the destination db"` ChunkSize uint64 `long:"chunk-size" description:"Chunk size for the migration in bytes"` + ErrorIfNoBboltDB bool `long:"error-if-no-bbolt-db-exists" description:"Return an error when a mandatory source bbolt DB is not found. By default missing source DBs are logged and skipped so a fresh node (no bbolt DBs yet) is treated as a successful no-op."` } func newMigrateDBCommand() *migrateDBCommand { @@ -232,13 +233,26 @@ func (x *migrateDBCommand) Execute(_ []string) error { // Open and check the database version. srcDb, err := openSourceDb(x.Source, prefix, x.Network, true) if err == kvdb.ErrDbDoesNotExist { - // Only skip if it's an optional because it's not - // required to run a wtclient or wtserver for example. - if optionalDBs[prefix] { + switch { + // Optional DBs (e.g. wtclient, wtserver, neutrino) are + // always safe to skip when missing. + case optionalDBs[prefix]: logger.Warnf("Skipping checking db version "+ "of optional DB %s: not found", prefix) continue + + // A mandatory bbolt DB is missing. Treat this as a + // fresh node by default (LND will create the SQL DB + // itself on first start). Only fail if the user + // explicitly opted in via --error-if-no-bbolt-db-exists. + case !x.ErrorIfNoBboltDB: + logger.Warnf("Skipping checking db version "+ + "of mandatory DB %s: not found "+ + "(pass --error-if-no-bbolt-db-exists "+ + "to fail instead)", prefix) + + continue } } if err != nil { @@ -281,12 +295,23 @@ func (x *migrateDBCommand) Execute(_ []string) error { x.Source, prefix, x.Network, true, ) if err == kvdb.ErrDbDoesNotExist { - // Only skip if it's an optional because it's not - // required to run a wtclient or wtserver for example. - if optionalDBs[prefix] { + switch { + // Optional DBs (e.g. wtclient, wtserver, neutrino) are + // always safe to skip when missing. + case optionalDBs[prefix]: logger.Warnf("Skipping optional DB %s: not "+ "found", prefix) continue + + // A mandatory bbolt DB is missing. Treat this as a + // fresh node by default (LND will create the SQL DB + // itself on first start). Only fail if the user + // explicitly opted in via --error-if-no-bbolt-db-exists. + case !x.ErrorIfNoBboltDB: + logger.Warnf("Skipping mandatory DB %s: not "+ + "found (pass --error-if-no-bbolt-db-"+ + "exists to fail instead)", prefix) + continue } } if err != nil { diff --git a/cmd_migrate_db_test.go b/cmd_migrate_db_test.go index bc8c3a0..98c1f53 100644 --- a/cmd_migrate_db_test.go +++ b/cmd_migrate_db_test.go @@ -3,6 +3,9 @@ package main import ( "testing" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/kvdb/sqlite" + "github.com/lightningnetwork/lnd/lncfg" "github.com/stretchr/testify/require" ) @@ -20,3 +23,54 @@ func setupTestData(t *testing.T) string { return tempDir } + +// TestMigrateDBNoBboltDBExists verifies that running migrate-db against a data +// directory that contains no bbolt source DBs is a successful no-op by default +// and only fails when --error-if-no-bbolt-db-exists is set. +func TestMigrateDBNoBboltDBExists(t *testing.T) { + t.Parallel() + + makeCmd := func(dataDir string, errIfMissing bool) *migrateDBCommand { + return &migrateDBCommand{ + Source: &SourceDB{ + Backend: lncfg.BoltBackend, + Bolt: &Bolt{ + DBTimeout: kvdb.DefaultDBTimeout, + DataDir: dataDir, + TowerDir: dataDir, + }, + }, + Dest: &DestDB{ + Backend: lncfg.SqliteBackend, + Sqlite: &Sqlite{ + DataDir: dataDir, + TowerDir: dataDir, + Config: &sqlite.Config{}, + }, + }, + Network: "regtest", + ChunkSize: 1024, + ErrorIfNoBboltDB: errIfMissing, + } + } + + t.Run("default skips missing DBs", func(t *testing.T) { + t.Parallel() + + // An empty temp dir contains no bbolt DBs at all. With the + // default flag value the migration should succeed without + // touching the destination. + err := makeCmd(t.TempDir(), false).Execute(nil) + require.NoError(t, err) + }) + + t.Run("flag forces error when missing", func(t *testing.T) { + t.Parallel() + + err := makeCmd(t.TempDir(), true).Execute(nil) + require.Error(t, err) + require.Contains( + t, err.Error(), "failed to open source db", + ) + }) +} diff --git a/docs/data-migration.md b/docs/data-migration.md index 3ea3cfb..843915b 100644 --- a/docs/data-migration.md +++ b/docs/data-migration.md @@ -238,6 +238,8 @@ Help Options: --force-verify-db Force a verification verifies two already marked (tombstoned and already migrated) dbs to make sure that the source db equals the content of the destination db --chunk-size= Chunk size for the migration in bytes + --error-if-no-bbolt-db-exists Return an error when a mandatory source bbolt DB is not found. By default missing source DBs are logged and skipped so a fresh node (no bbolt + DBs yet) is treated as a successful no-op. source: --source.backend=[bolt] The source database backend. (default: bolt)