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

feat(tools/cosmovisor): Introduce a new optional config variable for setting custom path to application data directory #21971

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
30 changes: 29 additions & 1 deletion tools/cosmovisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
EnvShutdownGrace = "DAEMON_SHUTDOWN_GRACE"
EnvSkipBackup = "UNSAFE_SKIP_BACKUP"
EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR"
EnvDataPath = "DAEMON_DATA_DIR"
EnvInterval = "DAEMON_POLL_INTERVAL"
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS"
Expand All @@ -45,6 +46,7 @@ const (
genesisDir = "genesis"
upgradesDir = "upgrades"
currentLink = "current"
dataDir = "data"

cfgFileName = "config"
cfgExtension = "toml"
Expand All @@ -62,6 +64,7 @@ type Config struct {
PollInterval time.Duration `toml:"daemon_poll_interval" mapstructure:"daemon_poll_interval" default:"300ms"`
UnsafeSkipBackup bool `toml:"unsafe_skip_backup" mapstructure:"unsafe_skip_backup" default:"false"`
DataBackupPath string `toml:"daemon_data_backup_dir" mapstructure:"daemon_data_backup_dir"`
DataPath string `toml:"daemon_data_dir" mapstructure:"daemon_data_dir"`
PreUpgradeMaxRetries int `toml:"daemon_preupgrade_max_retries" mapstructure:"daemon_preupgrade_max_retries" default:"0"`
DisableLogs bool `toml:"cosmovisor_disable_logs" mapstructure:"cosmovisor_disable_logs" default:"false"`
ColorLogs bool `toml:"cosmovisor_color_logs" mapstructure:"cosmovisor_color_logs" default:"true"`
Expand Down Expand Up @@ -106,7 +109,11 @@ func (cfg *Config) BaseUpgradeDir() string {

// UpgradeInfoFilePath is the expected upgrade-info filename created by `x/upgrade/keeper`.
func (cfg *Config) UpgradeInfoFilePath() string {
return filepath.Join(cfg.Home, "data", upgradetypes.UpgradeInfoFilename)
return filepath.Join(cfg.DataPath, upgradetypes.UpgradeInfoFilename)
}

func (cfg *Config) DefaultDataDirPath() string {
return filepath.Join(cfg.Home, dataDir)
}

// SymLinkToGenesis creates a symbolic link from "./current" to the genesis directory.
Expand Down Expand Up @@ -210,13 +217,18 @@ func GetConfigFromEnv(skipValidate bool) (*Config, error) {
Home: os.Getenv(EnvHome),
Name: os.Getenv(EnvName),
DataBackupPath: os.Getenv(EnvDataBackupPath),
DataPath: os.Getenv(EnvDataPath),
CustomPreUpgrade: os.Getenv(EnvCustomPreupgrade),
}

if cfg.DataBackupPath == "" {
cfg.DataBackupPath = cfg.Home
}

if cfg.DataPath == "" {
cfg.DataPath = cfg.DefaultDataDirPath()
}

var err error
if cfg.AllowDownloadBinaries, err = BooleanOption(EnvDownloadBin, false); err != nil {
errs = append(errs, err)
Expand Down Expand Up @@ -344,6 +356,20 @@ func (cfg *Config) validate() []error {
errs = append(errs, fmt.Errorf("%s is not a directory", cfg.Root()))
}
}
// validate DataPath
switch {
case cfg.DataPath == "":
errs = append(errs, fmt.Errorf("%s is not set", EnvDataPath))
case !filepath.IsAbs(cfg.DataPath):
errs = append(errs, fmt.Errorf("%s must be an absolute path", EnvDataPath))
default:
switch info, err := os.Stat(cfg.DataPath); {
case err != nil:
errs = append(errs, fmt.Errorf("%q must be a valid directory: %w", cfg.DataPath, err))
case !info.IsDir():
errs = append(errs, fmt.Errorf("%q must be a valid directory", cfg.DataPath))
}
}

// check the DataBackupPath
if cfg.UnsafeSkipBackup {
Expand Down Expand Up @@ -542,6 +568,7 @@ func (cfg Config) DetailString() string {
{EnvInterval, cfg.PollInterval.String()},
{EnvSkipBackup, fmt.Sprintf("%t", cfg.UnsafeSkipBackup)},
{EnvDataBackupPath, cfg.DataBackupPath},
{EnvDataPath, cfg.DataPath},
{EnvPreupgradeMaxRetries, fmt.Sprintf("%d", cfg.PreUpgradeMaxRetries)},
{EnvDisableLogs, fmt.Sprintf("%t", cfg.DisableLogs)},
{EnvColorLogs, fmt.Sprintf("%t", cfg.ColorLogs)},
Expand All @@ -556,6 +583,7 @@ func (cfg Config) DetailString() string {
{"Genesis Bin", cfg.GenesisBin()},
{"Monitored File", cfg.UpgradeInfoFilePath()},
{"Data Backup Dir", cfg.DataBackupPath},
{"Data Dir", cfg.DataPath},
}

var sb strings.Builder
Expand Down
207 changes: 127 additions & 80 deletions tools/cosmovisor/args_test.go

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion tools/cosmovisor/cmd/cosmovisor/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ func minConfigValidate(cfg *cosmovisor.Config) error {
if len(cfg.Name) == 0 {
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvName))
}

switch {
case len(cfg.Home) == 0:
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvHome))
Expand Down
4 changes: 2 additions & 2 deletions tools/cosmovisor/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ func (l Launcher) doBackup() error {

l.logger.Info("starting to take backup of data directory", "backup start time", st)

// copy the $DAEMON_HOME/data to a backup dir
if err = copy.Copy(filepath.Join(l.cfg.Home, "data"), dst); err != nil {
// copy the data dir to a backup dir
if err = copy.Copy(l.cfg.DataPath, dst); err != nil {
return fmt.Errorf("error while taking data backup: %w", err)
}

Expand Down
66 changes: 61 additions & 5 deletions tools/cosmovisor/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import (
func TestLaunchProcess(t *testing.T) {
// binaries from testdata/validate directory
home := copyTestData(t, "validate")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 15, UnsafeSkipBackup: true}
dataPath := filepath.Join(home, "data")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", DataPath: dataPath, PollInterval: 15, UnsafeSkipBackup: true}
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")

// should run the genesis binary and produce expected output
Expand Down Expand Up @@ -70,7 +71,8 @@ func TestLaunchProcess(t *testing.T) {
func TestPlanDisableRecase(t *testing.T) {
// binaries from testdata/validate directory
home := copyTestData(t, "norecase")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 20, UnsafeSkipBackup: true, DisableRecase: true}
dataPath := filepath.Join(home, "data")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", DataPath: dataPath, PollInterval: 20, UnsafeSkipBackup: true, DisableRecase: true}
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")

// should run the genesis binary and produce expected output
Expand Down Expand Up @@ -114,7 +116,8 @@ func TestPlanDisableRecase(t *testing.T) {
func TestLaunchProcessWithRestartDelay(t *testing.T) {
// binaries from testdata/validate directory
home := copyTestData(t, "validate")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", RestartDelay: 5 * time.Second, PollInterval: 20, UnsafeSkipBackup: true}
dataPath := filepath.Join(home, "data")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", DataPath: dataPath, RestartDelay: 5 * time.Second, PollInterval: 20, UnsafeSkipBackup: true}
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")

// should run the genesis binary and produce expected output
Expand Down Expand Up @@ -145,7 +148,8 @@ func TestLaunchProcessWithRestartDelay(t *testing.T) {
func TestPlanShutdownGrace(t *testing.T) {
// binaries from testdata/validate directory
home := copyTestData(t, "dontdie")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 15, UnsafeSkipBackup: true, ShutdownGrace: 2 * time.Second}
dataPath := filepath.Join(home, "data")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", DataPath: dataPath, PollInterval: 15, UnsafeSkipBackup: true, ShutdownGrace: 2 * time.Second}
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")

// should run the genesis binary and produce expected output
Expand Down Expand Up @@ -194,7 +198,8 @@ func TestLaunchProcessWithDownloads(t *testing.T) {
// chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir
// chain3-zip_dir - doesn't upgrade
home := copyTestData(t, "download")
cfg := &cosmovisor.Config{Home: home, Name: "autod", AllowDownloadBinaries: true, PollInterval: 100, UnsafeSkipBackup: true}
dataPath := filepath.Join(home, "data")
cfg := &cosmovisor.Config{Home: home, Name: "autod", DataPath: dataPath, AllowDownloadBinaries: true, PollInterval: 100, UnsafeSkipBackup: true}
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmovisor")
upgradeFilename := cfg.UpgradeInfoFilePath()

Expand Down Expand Up @@ -257,9 +262,11 @@ func TestLaunchProcessWithDownloadsAndMissingPreupgrade(t *testing.T) {
// chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir
// chain3-zip_dir - doesn't upgrade
home := copyTestData(t, "download")
dataPath := filepath.Join(home, "data")
cfg := &cosmovisor.Config{
Home: home,
Name: "autod",
DataPath: dataPath,
AllowDownloadBinaries: true,
PollInterval: 100,
UnsafeSkipBackup: true,
Expand Down Expand Up @@ -293,9 +300,11 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) {
// chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir
// chain3-zip_dir - doesn't upgrade
home := copyTestData(t, "download")
dataPath := filepath.Join(home, "data")
cfg := &cosmovisor.Config{
Home: home,
Name: "autod",
DataPath: dataPath,
AllowDownloadBinaries: true,
PollInterval: 100,
UnsafeSkipBackup: true,
Expand Down Expand Up @@ -362,6 +371,53 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) {
require.Equal(t, cfg.UpgradeBin("chain3"), currentBin)
}

// TestPlanCustomDataLocation will detect upgrade when chain data dir is not located in the default path
// the custom path should be provided as an absolute path to the configs
func TestPlanCustomDataLocation(t *testing.T) {
// binaries from testdata/validate directory
home := copyTestData(t, "custom-data-path")
dataPath := filepath.Join(home, "custom-location/data")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", DataPath: dataPath, PollInterval: 20, UnsafeSkipBackup: true}
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")

// should run the genesis binary and produce expected output
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
require.Equal(t, cfg.GenesisBin(), currentBin)

launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(t, err)

upgradeFile := cfg.UpgradeInfoFilePath()

args := []string{"foo", "bar", "1234", upgradeFile}
doUpgrade, err := launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.True(t, doUpgrade)
require.Equal(t, "", stderr.String())
require.Equal(t, fmt.Sprintf("Genesis foo bar 1234 %s\nUPGRADE \"chain2\" NEEDED at height: 49: {}\n", upgradeFile), stdout.String())

// ensure this is upgraded now and produces new output
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)

require.Equal(t, cfg.UpgradeBin("chain2"), currentBin)
args = []string{"second", "run", "--verbose"}
stdout.Reset()
stderr.Reset()

doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.False(t, doUpgrade)
require.Equal(t, "", stderr.String())
require.Equal(t, "chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String())

// ended without other upgrade
require.Equal(t, cfg.UpgradeBin("chain2"), currentBin)
}

// TestSkipUpgrade tests heights that are identified to be skipped and return if upgrade height matches the skip heights
func TestSkipUpgrade(t *testing.T) {
cases := []struct {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh

echo Genesis $@
sleep 1
test -z $4 && exit 1001
echo 'UPGRADE "chain2" NEEDED at height: 49: {}'
echo '{"name":"chain2","height":49,"info":""}' > $4
sleep 2
echo Never should be printed!!!
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh

echo chain 2 is live!
echo Args: $@
sleep 1
echo Finished successfully
Loading