Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: move crash and stateproof DB defaults to hot dir #5817

Merged
merged 8 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
124 changes: 117 additions & 7 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,12 @@ func TestEnsureAbsDir(t *testing.T) {
require.Equal(t, testDirectory+"/myGenesisID", t2Abs)
}

type tLogger struct{ t *testing.T }

func (l tLogger) Infof(fmts string, args ...interface{}) {
l.t.Logf(fmts, args...)
}

algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
// TestEnsureAndResolveGenesisDirs confirms that paths provided in the config are resolved to absolute paths and are created if relevant
func TestEnsureAndResolveGenesisDirs(t *testing.T) {
partitiontest.PartitionTest(t)
Expand All @@ -689,7 +695,7 @@ func TestEnsureAndResolveGenesisDirs(t *testing.T) {
cfg.StateproofDir = filepath.Join(testDirectory, "/RELATIVEPATHS/../RELATIVE/../custom_stateproof")
cfg.CatchpointDir = filepath.Join(testDirectory, "custom_catchpoint")

paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID")
paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t})
require.NoError(t, err)

// confirm that the paths are absolute, and contain the genesisID
Expand All @@ -711,7 +717,7 @@ func TestEnsureAndResolveGenesisDirs_hierarchy(t *testing.T) {

cfg := GetDefaultLocal()
testDirectory := t.TempDir()
paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID")
paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t})
require.NoError(t, err)
// confirm that if only the root is specified, it is used for all directories
require.Equal(t, testDirectory+"/myGenesisID", paths.TrackerGenesisDir)
Expand All @@ -731,21 +737,125 @@ func TestEnsureAndResolveGenesisDirs_hierarchy(t *testing.T) {
cold := filepath.Join(testDirectory, "cold")
cfg.HotDataDir = hot
cfg.ColdDataDir = cold
paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID")
paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t})
require.NoError(t, err)
// confirm that if hot/cold are specified, hot/cold are used for appropriate directories
require.Equal(t, hot+"/myGenesisID", paths.TrackerGenesisDir)
require.DirExists(t, paths.TrackerGenesisDir)
require.Equal(t, cold+"/myGenesisID", paths.BlockGenesisDir)
require.DirExists(t, paths.BlockGenesisDir)
require.Equal(t, cold+"/myGenesisID", paths.CrashGenesisDir)
require.Equal(t, hot+"/myGenesisID", paths.CrashGenesisDir)
require.DirExists(t, paths.CrashGenesisDir)
require.Equal(t, cold+"/myGenesisID", paths.StateproofGenesisDir)
require.Equal(t, hot+"/myGenesisID", paths.StateproofGenesisDir)
require.DirExists(t, paths.StateproofGenesisDir)
require.Equal(t, cold+"/myGenesisID", paths.CatchpointGenesisDir)
require.DirExists(t, paths.CatchpointGenesisDir)
}

func TestEnsureAndResolveGenesisDirs_migrate(t *testing.T) {
partitiontest.PartitionTest(t)

cfg := GetDefaultLocal()
testDirectory := t.TempDir()
cfg.HotDataDir = filepath.Join(testDirectory, "hot")
cfg.ColdDataDir = filepath.Join(testDirectory, "cold")
coldDir := filepath.Join(cfg.ColdDataDir, "myGenesisID")
hotDir := filepath.Join(cfg.HotDataDir, "myGenesisID")
err := os.MkdirAll(coldDir, 0755)
require.NoError(t, err)
// put a crash.sqlite file in the ColdDataDir
err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite"), []byte("test"), 0644)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite-shm"), []byte("test"), 0644)
require.NoError(t, err)
// put a stateproof.sqlite file in the ColdDataDir
err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite"), []byte("test"), 0644)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite-wal"), []byte("test"), 0644)
require.NoError(t, err)
// Resolve
paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t})
require.NoError(t, err)
// Confirm that crash.sqlite was moved to HotDataDir
require.DirExists(t, paths.CrashGenesisDir)
require.Equal(t, hotDir, paths.CrashGenesisDir)
require.NoFileExists(t, filepath.Join(coldDir, "crash.sqlite"))
require.NoFileExists(t, filepath.Join(coldDir, "crash.sqlite-shm"))
require.FileExists(t, filepath.Join(hotDir, "crash.sqlite"))
require.FileExists(t, filepath.Join(hotDir, "crash.sqlite-shm"))
// Confirm that stateproof.sqlite was moved to HotDataDir
require.DirExists(t, paths.StateproofGenesisDir)
require.Equal(t, hotDir, paths.StateproofGenesisDir)
require.NoFileExists(t, filepath.Join(coldDir, "stateproof.sqlite"))
require.NoFileExists(t, filepath.Join(coldDir, "stateproof.sqlite-wal"))
require.FileExists(t, filepath.Join(hotDir, "stateproof.sqlite"))
require.FileExists(t, filepath.Join(hotDir, "stateproof.sqlite-wal"))
}

func TestEnsureAndResolveGenesisDirs_migrateCrashFail(t *testing.T) {
partitiontest.PartitionTest(t)

cfg := GetDefaultLocal()
testDirectory := t.TempDir()
cfg.HotDataDir = filepath.Join(testDirectory, "hot")
cfg.ColdDataDir = filepath.Join(testDirectory, "cold")
coldDir := filepath.Join(cfg.ColdDataDir, "myGenesisID")
hotDir := filepath.Join(cfg.HotDataDir, "myGenesisID")
err := os.MkdirAll(coldDir, 0755)
require.NoError(t, err)
err = os.MkdirAll(hotDir, 0755)
require.NoError(t, err)
// put a crash.sqlite file in the ColdDataDir
err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite"), []byte("test"), 0644)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(coldDir, "crash.sqlite-shm"), []byte("test"), 0644)
require.NoError(t, err)
// also put a crash.sqlite file in the HotDataDir
err = os.WriteFile(filepath.Join(hotDir, "crash.sqlite"), []byte("test"), 0644)
require.NoError(t, err)
// Resolve
paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t})
require.Error(t, err)
require.Empty(t, paths)
// Confirm that crash.sqlite was not moved to HotDataDir
require.FileExists(t, filepath.Join(coldDir, "crash.sqlite"))
require.FileExists(t, filepath.Join(coldDir, "crash.sqlite-shm"))
require.FileExists(t, filepath.Join(hotDir, "crash.sqlite"))
require.NoFileExists(t, filepath.Join(hotDir, "crash.sqlite-shm"))
}

func TestEnsureAndResolveGenesisDirs_migrateSPFail(t *testing.T) {
partitiontest.PartitionTest(t)

cfg := GetDefaultLocal()
testDirectory := t.TempDir()
cfg.HotDataDir = filepath.Join(testDirectory, "hot")
cfg.ColdDataDir = filepath.Join(testDirectory, "cold")
coldDir := filepath.Join(cfg.ColdDataDir, "myGenesisID")
hotDir := filepath.Join(cfg.HotDataDir, "myGenesisID")
err := os.MkdirAll(coldDir, 0755)
require.NoError(t, err)
err = os.MkdirAll(hotDir, 0755)
require.NoError(t, err)
// put a stateproof.sqlite file in the ColdDataDir
err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite"), []byte("test"), 0644)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(coldDir, "stateproof.sqlite-wal"), []byte("test"), 0644)
require.NoError(t, err)
// also put a stateproof.sqlite-wal file in the HotDataDir
err = os.WriteFile(filepath.Join(hotDir, "stateproof.sqlite-wal"), []byte("test"), 0644)
require.NoError(t, err)
// Resolve
paths, err := cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t})
require.Error(t, err)
require.Empty(t, paths)
// Confirm that stateproof.sqlite was not moved to HotDataDir
require.FileExists(t, filepath.Join(coldDir, "stateproof.sqlite"))
require.FileExists(t, filepath.Join(coldDir, "stateproof.sqlite-wal"))
require.NoFileExists(t, filepath.Join(hotDir, "stateproof.sqlite"))
require.FileExists(t, filepath.Join(hotDir, "stateproof.sqlite-wal"))
}

// TestEnsureAndResolveGenesisDirsError confirms that if a path can't be created, an error is returned
func TestEnsureAndResolveGenesisDirsError(t *testing.T) {
partitiontest.PartitionTest(t)
Expand All @@ -761,15 +871,15 @@ func TestEnsureAndResolveGenesisDirsError(t *testing.T) {
cfg.CatchpointDir = filepath.Join(testDirectory, "custom_catchpoint")

// first try an error with an empty root dir
paths, err := cfg.EnsureAndResolveGenesisDirs("", "myGenesisID")
paths, err := cfg.EnsureAndResolveGenesisDirs("", "myGenesisID", tLogger{t: t})
require.Empty(t, paths)
require.Error(t, err)
require.Contains(t, err.Error(), "rootDir is required")

require.NoError(t, os.Chmod(testDirectory, 0200))

// now try an error with a root dir that can't be written to
paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID")
paths, err = cfg.EnsureAndResolveGenesisDirs(testDirectory, "myGenesisID", tLogger{t: t})
require.Empty(t, paths)
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
Expand Down
57 changes: 48 additions & 9 deletions config/localTemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@
// For isolation, the node will create a subdirectory in this location, named by the genesis-id of the network.
// If not specified, the node will use the ColdDataDir.
CatchpointDir string `version[31]:""`
// StateproofDir is an optional directory to store stateproof data.
// StateproofDir is an optional directory to persist state about observed and issued state proof messages.
// For isolation, the node will create a subdirectory in this location, named by the genesis-id of the network.
// If not specified, the node will use the ColdDataDir.
// If not specified, the node will use the HotDataDir.
StateproofDir string `version[31]:""`
// CrashDBDir is an optional directory to store the crash database.
// CrashDBDir is an optional directory to persist agreement's consensus participation state.
// For isolation, the node will create a subdirectory in this location, named by the genesis-id of the network.
// If not specified, the node will use the ColdDataDir.
// If not specified, the node will use the HotDataDir
CrashDBDir string `version[31]:""`

// LogFileDir is an optional directory to store the log, node.log
Expand Down Expand Up @@ -771,9 +771,13 @@
return liveLog, archive
}

type logger interface {
Infof(format string, args ...interface{})
}
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved

// EnsureAndResolveGenesisDirs will resolve the supplied config paths to absolute paths, and will create the genesis directories of each
// returns a ResolvedGenesisDirs struct with the resolved paths for use during runtime
func (cfg *Local) EnsureAndResolveGenesisDirs(rootDir, genesisID string) (ResolvedGenesisDirs, error) {
func (cfg *Local) EnsureAndResolveGenesisDirs(rootDir, genesisID string, logger logger) (ResolvedGenesisDirs, error) {
var resolved ResolvedGenesisDirs
var err error
if rootDir != "" {
Expand Down Expand Up @@ -829,27 +833,62 @@
} else {
resolved.CatchpointGenesisDir = resolved.ColdGenesisDir
}
// if StateproofDir is not set, use ColdDataDir
// if StateproofDir is not set, use HotDataDir
if cfg.StateproofDir != "" {
resolved.StateproofGenesisDir, err = ensureAbsGenesisDir(cfg.StateproofDir, genesisID)
if err != nil {
return ResolvedGenesisDirs{}, err
}
} else {
resolved.StateproofGenesisDir = resolved.ColdGenesisDir
resolved.StateproofGenesisDir = resolved.HotGenesisDir
// if separate HotDataDir and ColdDataDir was configured, but StateproofDir was not configured
if resolved.ColdGenesisDir != resolved.HotGenesisDir {
// move existing stateproof DB files from ColdDataDir to HotDataDir
moveErr := moveDirIfExists(logger, resolved.ColdGenesisDir, resolved.HotGenesisDir, "stateproof.sqlite", "stateproof.sqlite-shm", "stateproof.sqlite-wal")
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
if moveErr != nil {
return ResolvedGenesisDirs{}, fmt.Errorf("Error moving stateproof DB files from ColdDataDir %s to HotDataDir %s: %v", resolved.ColdGenesisDir, resolved.HotGenesisDir, moveErr)
}
}
}
// if CrashDBDir is not set, use ColdDataDir
// if CrashDBDir is not set, use HotDataDir
if cfg.CrashDBDir != "" {
resolved.CrashGenesisDir, err = ensureAbsGenesisDir(cfg.CrashDBDir, genesisID)
if err != nil {
return ResolvedGenesisDirs{}, err
}
} else {
resolved.CrashGenesisDir = resolved.ColdGenesisDir
resolved.CrashGenesisDir = resolved.HotGenesisDir
// if separate HotDataDir and ColdDataDir was configured, but CrashDBDir was not configured
if resolved.ColdGenesisDir != resolved.HotGenesisDir {
// move existing crash DB files from ColdDataDir to HotDataDir
moveErr := moveDirIfExists(logger, resolved.ColdGenesisDir, resolved.HotGenesisDir, "crash.sqlite", "crash.sqlite-shm", "crash.sqlite-wal")
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
if moveErr != nil {
return ResolvedGenesisDirs{}, fmt.Errorf("Error moving crash DB files from ColdDataDir %s to HotDataDir %s: %v", resolved.ColdGenesisDir, resolved.HotGenesisDir, moveErr)
}
}
}
return resolved, nil
}

func moveDirIfExists(logger logger, srcdir, dstdir string, files ...string) error {
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
// first, check if any files already exist in dstdir, and quit if so
for _, file := range files {
if _, err := os.Stat(filepath.Join(dstdir, file)); err == nil {
return fmt.Errorf("destination file %s already exists, not overwriting", filepath.Join(dstdir, file))
}
}
// then, check if any files exist in srcdir, and move them to dstdir
for _, file := range files {
if _, err := os.Stat(filepath.Join(srcdir, file)); err == nil {
if err := os.Rename(filepath.Join(srcdir, file), filepath.Join(dstdir, file)); err != nil {
return fmt.Errorf("failed to move file %s from %s to %s: %v", file, srcdir, dstdir, err)

Check warning on line 884 in config/localTemplate.go

View check run for this annotation

Codecov / codecov/patch

config/localTemplate.go#L884

Added line #L884 was not covered by tests
}
logger.Infof("Moved DB file %s from ColdDataDir %s to HotDataDir %s", file, srcdir, dstdir)
}
}
return nil
}

// AdjustConnectionLimits updates RestConnectionsSoftLimit, RestConnectionsHardLimit, IncomingConnectionsLimit
// if requiredFDs greater than maxFDs
func (cfg *Local) AdjustConnectionLimits(requiredFDs, maxFDs uint64) bool {
Expand Down
2 changes: 1 addition & 1 deletion node/follower_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo
node.genesisHash = genesis.Hash()
node.devMode = genesis.DevMode
var err error
node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID())
node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID(), log)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd
node.devMode = genesis.DevMode
node.config = cfg
var err error
node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID())
node.genesisDirs, err = cfg.EnsureAndResolveGenesisDirs(rootDir, genesis.ID(), log)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions node/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ func TestConfiguredDataDirs(t *testing.T) {
require.FileExists(t, filepath.Join(testDirHot, genesis.ID(), "ledger.tracker.sqlite"))

// confirm the stateproof db in the genesis dir of hot data dir
require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "stateproof.sqlite"))
require.FileExists(t, filepath.Join(testDirHot, genesis.ID(), "stateproof.sqlite"))

// confirm cold data dir exists and contains a genesis dir
require.DirExists(t, filepath.Join(testDirCold, genesis.ID()))
Expand All @@ -609,8 +609,8 @@ func TestConfiguredDataDirs(t *testing.T) {
// confirm the partregistry is in the genesis dir of cold data dir
require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "partregistry.sqlite"))

// confirm the partregistry is in the genesis dir of cold data dir
require.FileExists(t, filepath.Join(testDirCold, genesis.ID(), "crash.sqlite"))
// confirm the agreement crash DB is in the genesis dir of hot data dir
require.FileExists(t, filepath.Join(testDirHot, genesis.ID(), "crash.sqlite"))
}

// TestConfiguredResourcePaths tests to see that when TrackerDbFilePath, BlockDbFilePath, StateproofDir, and CrashFilePath are set, underlying resources are created in the correct locations
Expand Down