From f4bb7c13c046c43c705840b6539b4e70c1fdefd1 Mon Sep 17 00:00:00 2001 From: Artur Troian Date: Thu, 3 Oct 2024 08:20:51 -0500 Subject: [PATCH] feat(tools/cosmovisor): create current symlink as relative Signed-off-by: Artur Troian --- tools/cosmovisor/CHANGELOG.md | 1 + tools/cosmovisor/args.go | 39 ++-- tools/cosmovisor/args_test.go | 58 +++--- tools/cosmovisor/cmd/cosmovisor/init.go | 8 +- tools/cosmovisor/cmd/cosmovisor/init_test.go | 109 ++++++---- tools/cosmovisor/cmd/cosmovisor/root.go | 2 +- tools/cosmovisor/cmd/cosmovisor/run.go | 7 + tools/cosmovisor/process_test.go | 205 ++++++++++++++----- tools/cosmovisor/upgrade_test.go | 137 +++++++++---- 9 files changed, 391 insertions(+), 175 deletions(-) diff --git a/tools/cosmovisor/CHANGELOG.md b/tools/cosmovisor/CHANGELOG.md index 3db3e4ea9954..1285007e6d84 100644 --- a/tools/cosmovisor/CHANGELOG.md +++ b/tools/cosmovisor/CHANGELOG.md @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* [#21891](https://github.com/cosmos/cosmos-sdk/pull/21891) create `current` symlink as relative * [#21462](https://github.com/cosmos/cosmos-sdk/pull/21462) Pass `stdin` to binary. ### Features diff --git a/tools/cosmovisor/args.go b/tools/cosmovisor/args.go index 6c6b5b7b0572..577a16f251fc 100644 --- a/tools/cosmovisor/args.go +++ b/tools/cosmovisor/args.go @@ -113,14 +113,19 @@ func (cfg *Config) UpgradeInfoFilePath() string { // SymLinkToGenesis creates a symbolic link from "./current" to the genesis directory. func (cfg *Config) SymLinkToGenesis() (string, error) { - genesis := filepath.Join(cfg.Root(), genesisDir) - link := filepath.Join(cfg.Root(), currentLink) + // workdir is set to cosmovisor directory so relative + // symlinks are getting resolved correctly + if err := os.Symlink(genesisDir, currentLink); err != nil { + return "", err + } - if err := os.Symlink(genesis, link); err != nil { + res, err := filepath.EvalSymlinks(cfg.GenesisBin()) + if err != nil { return "", err } + // and return the genesis binary - return cfg.GenesisBin(), nil + return res, nil } // WaitRestartDelay will block and wait until the RestartDelay has elapsed. @@ -134,27 +139,24 @@ func (cfg *Config) WaitRestartDelay() { // This will resolve the symlink to the underlying directory to make it easier to debug func (cfg *Config) CurrentBin() (string, error) { cur := filepath.Join(cfg.Root(), currentLink) + // if nothing here, fallback to genesis - info, err := os.Lstat(cur) - if err != nil { - // Create symlink to the genesis - return cfg.SymLinkToGenesis() - } // if it is there, ensure it is a symlink - if info.Mode()&os.ModeSymlink == 0 { + info, err := os.Lstat(cur) + if err != nil || (info.Mode()&os.ModeSymlink == 0) { // Create symlink to the genesis return cfg.SymLinkToGenesis() } - // resolve it - dest, err := os.Readlink(cur) + res, err := filepath.EvalSymlinks(cur) if err != nil { // Create symlink to the genesis return cfg.SymLinkToGenesis() } // and return the binary - binpath := filepath.Join(dest, "bin", cfg.Name) + binpath := filepath.Join(res, "bin", cfg.Name) + return binpath, nil } @@ -385,24 +387,23 @@ func (cfg *Config) SetCurrentUpgrade(u upgradetypes.Plan) (rerr error) { } // set a symbolic link - link := filepath.Join(cfg.Root(), currentLink) safeName := url.PathEscape(u.Name) - upgrade := filepath.Join(cfg.Root(), upgradesDir, safeName) + upgrade := filepath.Join(upgradesDir, safeName) // remove link if it exists - if _, err := os.Stat(link); err == nil { - if err := os.Remove(link); err != nil { + if _, err := os.Stat(currentLink); err == nil { + if err := os.Remove(currentLink); err != nil { return fmt.Errorf("failed to remove existing link: %w", err) } } // point to the new directory - if err := os.Symlink(upgrade, link); err != nil { + if err := os.Symlink(upgrade, currentLink); err != nil { return fmt.Errorf("creating current symlink: %w", err) } cfg.currentUpgrade = u - f, err := os.Create(filepath.Join(upgrade, upgradetypes.UpgradeInfoFilename)) + f, err := os.Create(filepath.Join(cfg.Root(), upgrade, upgradetypes.UpgradeInfoFilename)) if err != nil { return err } diff --git a/tools/cosmovisor/args_test.go b/tools/cosmovisor/args_test.go index f121a3989d9f..12c28b8e7952 100644 --- a/tools/cosmovisor/args_test.go +++ b/tools/cosmovisor/args_test.go @@ -454,6 +454,7 @@ var newConfig = func( skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, + grpcAddress string, disableLogs, colorLogs bool, timeFormatLogs string, customPreUpgrade string, @@ -470,6 +471,7 @@ var newConfig = func( PollInterval: time.Millisecond * time.Duration(interval), UnsafeSkipBackup: skipBackup, DataBackupPath: dataBackupPath, + GRPCAddress: grpcAddress, PreUpgradeMaxRetries: preupgradeMaxRetries, DisableLogs: disableLogs, ColorLogs: colorLogs, @@ -518,7 +520,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "all good", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "true", "10s"}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", true, 10000000000), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", true, 10000000000), expectedErrCount: 0, }, { @@ -538,25 +540,25 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "download bin not set", envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "", false, 0), expectedErrCount: 0, }, { name: "download bin true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "download bin false", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "download ensure checksum true", envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -568,19 +570,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "restart upgrade not set", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "restart upgrade true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "restart upgrade true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -592,19 +594,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "skip unsafe backups not set", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "skip unsafe backups true", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "skip unsafe backups false", envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false, true, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -622,7 +624,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "poll interval not set", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -634,7 +636,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "poll interval 1s", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -658,7 +660,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "restart delay not set", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -670,7 +672,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "restart delay 1s", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -688,19 +690,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "prepupgrade max retries 0", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "prepupgrade max retries not set", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "prepupgrade max retries 5", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, false, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -712,7 +714,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "disable logs good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -724,19 +726,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "disable logs color good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, time.Kitchen, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "disable logs timestamp", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, false, "", "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, "", "preupgrade.sh", false, 0), expectedErrCount: 0, }, { name: "enable rf3339 logs timestamp", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339, "preupgrade.sh", false, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", false, 0), expectedErrCount: 0, }, { @@ -748,7 +750,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "disable recase good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", ""}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339, "preupgrade.sh", true, 0), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 0), expectedErrCount: 0, }, { @@ -759,7 +761,7 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "shutdown grace good", envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", "15s"}, - expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true, true, time.RFC3339, "preupgrade.sh", true, 15000000000), + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 15000000000), expectedErrCount: 0, }, } @@ -794,7 +796,7 @@ func (s *argsTestSuite) setUpDir() string { func (s *argsTestSuite) setupConfig(home string) string { s.T().Helper() - cfg := newConfig(home, "test", true, true, true, 406, false, home, 8, 0, false, true, "kitchen", "", true, 10000000000) + cfg := newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, "kitchen", "", true, 10000000000) path := filepath.Join(home, rootName, "config.toml") f, err := os.Create(path) s.Require().NoError(err) @@ -824,7 +826,7 @@ func (s *argsTestSuite) TestConfigFromFile() { { name: "valid config", expectedCfg: func() *Config { - return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, false, true, time.Kitchen, "", true, 10000000000) + return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000) }, filePath: cfgFilePath, expectedError: "", @@ -839,13 +841,13 @@ func (s *argsTestSuite) TestConfigFromFile() { os.Setenv(EnvName, "env-name") }, expectedCfg: func() *Config { - return newConfig(home, "env-name", true, true, true, 406, false, home, 8, 0, false, true, time.Kitchen, "", true, 10000000000) + return newConfig(home, "env-name", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000) }, }, { name: "empty config file path will load config from ENV variables", expectedCfg: func() *Config { - return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, false, true, time.Kitchen, "", true, 10000000000) + return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000) }, filePath: "", expectedError: "", diff --git a/tools/cosmovisor/cmd/cosmovisor/init.go b/tools/cosmovisor/cmd/cosmovisor/init.go index 5f78e2845c02..90a35ca286fa 100644 --- a/tools/cosmovisor/cmd/cosmovisor/init.go +++ b/tools/cosmovisor/cmd/cosmovisor/init.go @@ -14,7 +14,7 @@ import ( "cosmossdk.io/x/upgrade/plan" ) -func NewIntCmd() *cobra.Command { +func NewInitCmd() *cobra.Command { initCmd := &cobra.Command{ Use: "init ", Short: "Initialize a cosmovisor daemon home directory.", @@ -93,6 +93,12 @@ func InitializeCosmovisor(logger log.Logger, args []string) error { return err } + // set current working directory to $DAEMON_NAME/cosmosvisor + // to allow current symlink to be relative + if err = os.Chdir(cfg.Root()); err != nil { + return fmt.Errorf("failed to change directory to %s: %w", cfg.Root(), err) + } + logger.Info("checking on the current symlink and creating it if needed") cur, curErr := cfg.CurrentBin() if curErr != nil { diff --git a/tools/cosmovisor/cmd/cosmovisor/init_test.go b/tools/cosmovisor/cmd/cosmovisor/init_test.go index 97a082db729b..35a73348cc39 100644 --- a/tools/cosmovisor/cmd/cosmovisor/init_test.go +++ b/tools/cosmovisor/cmd/cosmovisor/init_test.go @@ -138,8 +138,8 @@ func (s *InitTestSuite) readStdInpFromFile(data []byte) { } var ( - _ io.Reader = BufferedPipe{} - _ io.Writer = BufferedPipe{} + _ io.Reader = &BufferedPipe{} + _ io.Writer = &BufferedPipe{} ) // BufferedPipe contains a connected read/write pair of files (a pipe), @@ -167,8 +167,8 @@ type BufferedPipe struct { // NewBufferedPipe creates a new BufferedPipe with the given name. // Files must be closed once you are done with them (e.g. with .Close()). // Once ready, buffering must be started using .Start(). See also StartNewBufferedPipe. -func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) { - p := BufferedPipe{Name: name} +func NewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) { + p := &BufferedPipe{Name: name} p.Reader, p.Writer, p.Error = os.Pipe() if p.Error != nil { return p, p.Error @@ -184,7 +184,7 @@ func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error // // p, _ := NewBufferedPipe(name, replicateTo...) // p.Start() -func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) { +func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) { p, err := NewBufferedPipe(name, replicateTo...) if err != nil { return p, err @@ -214,6 +214,7 @@ func (p *BufferedPipe) Start() { if _, p.Error = io.Copy(&b, p.BufferReader); p.Error != nil { b.WriteString("buffer error: " + p.Error.Error()) } + p.buffer <- b.Bytes() }() p.started = true @@ -238,6 +239,7 @@ func (p *BufferedPipe) Collect() []byte { panic("buffered pipe " + p.Name + " has not been started: cannot collect") } _ = p.Writer.Close() + if p.buffer == nil { return []byte{} } @@ -247,12 +249,12 @@ func (p *BufferedPipe) Collect() []byte { } // Read implements the io.Reader interface on this BufferedPipe. -func (p BufferedPipe) Read(bz []byte) (n int, err error) { +func (p *BufferedPipe) Read(bz []byte) (n int, err error) { return p.Reader.Read(bz) } // Write implements the io.Writer interface on this BufferedPipe. -func (p BufferedPipe) Write(bz []byte) (n int, err error) { +func (p *BufferedPipe) Write(bz []byte) (n int, err error) { return p.Writer.Write(bz) } @@ -274,7 +276,7 @@ func (s *InitTestSuite) NewCapturingLogger() (*BufferedPipe, log.Logger) { bufferedStdOut, err := StartNewBufferedPipe("stdout", os.Stdout) s.Require().NoError(err, "creating stdout buffered pipe") logger := log.NewLogger(bufferedStdOut, log.ColorOption(false), log.TimeFormatOption(time.RFC3339Nano)).With(log.ModuleKey, cosmovisorDirName) - return &bufferedStdOut, logger + return bufferedStdOut, logger } // CreateHelloWorld creates a shell script that outputs HELLO WORLD. @@ -443,15 +445,13 @@ func (s *InitTestSuite) TestInitializeCosmovisorInvalidExisting() { rootDir := filepath.Join(env.Home, cosmovisorDirName) require.NoError(t, os.MkdirAll(rootDir, 0o755)) curLn := filepath.Join(rootDir, "current") - genDir := filepath.Join(rootDir, "genesis") require.NoError(t, copyFile(hwExe, curLn)) - expErr := fmt.Sprintf("symlink %s %s: file exists", genDir, curLn) s.setEnv(t, env) buffer, logger := s.NewCapturingLogger() logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) err := InitializeCosmovisor(logger, []string{hwExe}) - require.EqualError(t, err, expErr, "calling InitializeCosmovisor") + require.EqualError(t, err, "symlink genesis current: file exists", "calling InitializeCosmovisor") bufferBz := buffer.Collect() bufferStr := string(bufferBz) assert.Contains(t, bufferStr, "checking on the current symlink and creating it if needed") @@ -484,37 +484,43 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { hwExe := s.CreateHelloWorld(0o755) s.T().Run("starting with blank slate", func(t *testing.T) { - testDir := s.T().TempDir() - env := &cosmovisorInitEnv{ - Home: filepath.Join(testDir, "home"), + env := s.prepareConfig(s.T(), cosmovisorInitEnv{ Name: "blank", - } + }) + curLn := filepath.Join(env.Home, cosmovisorDirName, "current") - genBinDir := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin") - genBinExe := filepath.Join(genBinDir, env.Name) + + s.setEnv(s.T(), env) + buffer, logger := s.NewCapturingLogger() + logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) + err := InitializeCosmovisor(logger, []string{hwNonExe}) + require.NoError(t, err, "calling InitializeCosmovisor") + + genDir := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin") + genBinExe := filepath.Join(genDir, env.Name) + + genBinDirEval, err := filepath.EvalSymlinks(genDir) + require.NoError(t, err) + + genBinEvalExe := filepath.Join(genBinDirEval, env.Name) + expInLog := []string{ "checking on the genesis/bin directory", - fmt.Sprintf("creating directory (and any parents): %q", genBinDir), + fmt.Sprintf("creating directory (and any parents): %q", genDir), "checking on the genesis/bin executable", fmt.Sprintf("copying executable into place: %q", genBinExe), fmt.Sprintf("making sure %q is executable", genBinExe), "checking on the current symlink and creating it if needed", - fmt.Sprintf("the current symlink points to: %q", genBinExe), + fmt.Sprintf("the current symlink points to: %q", genBinEvalExe), fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)), } - s.setEnv(s.T(), env) - buffer, logger := s.NewCapturingLogger() - logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) - err := InitializeCosmovisor(logger, []string{hwNonExe}) - require.NoError(t, err, "calling InitializeCosmovisor") - - _, err = os.Stat(genBinDir) - assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDir) + _, err = os.Stat(genBinDirEval) + assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDirEval) _, err = os.Stat(curLn) assert.NoError(t, err, "statting the current link: %q", curLn) - exeInfo, exeErr := os.Stat(genBinExe) - if assert.NoError(t, exeErr, "statting the executable: %q", genBinExe) { + exeInfo, exeErr := os.Stat(genBinEvalExe) + if assert.NoError(t, exeErr, "statting the executable: %q", genBinEvalExe) { assert.True(t, exeInfo.Mode().IsRegular(), "executable is regular file") // Check if the world-executable bit is set. exePermMask := exeInfo.Mode().Perm() & 0o001 @@ -534,10 +540,18 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { Name: "nocur", } rootDir := filepath.Join(env.Home, cosmovisorDirName) + genBinDir := filepath.Join(rootDir, "genesis", "bin") genBinDirExe := filepath.Join(genBinDir, env.Name) + require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir") require.NoError(t, copyFile(hwExe, genBinDirExe), "copying executable to genesis") + + genBinDirEval, err := filepath.EvalSymlinks(genBinDir) + require.NoError(t, err) + + genBinEvalExe := filepath.Join(genBinDirEval, env.Name) + upgradesDir := filepath.Join(rootDir, "upgrades") for i := 1; i <= 5; i++ { upgradeBinDir := filepath.Join(upgradesDir, fmt.Sprintf("upgrade-%02d", i), "bin") @@ -552,14 +566,14 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { "checking on the genesis/bin executable", fmt.Sprintf("the %q file already exists", genBinDirExe), fmt.Sprintf("making sure %q is executable", genBinDirExe), - fmt.Sprintf("the current symlink points to: %q", genBinDirExe), + fmt.Sprintf("the current symlink points to: %q", genBinEvalExe), fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)), } s.setEnv(t, env) buffer, logger := s.NewCapturingLogger() logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) - err := InitializeCosmovisor(logger, []string{hwExe}) + err = InitializeCosmovisor(logger, []string{hwExe}) require.NoError(t, err, "calling InitializeCosmovisor") bufferBz := buffer.Collect() bufferStr := string(bufferBz) @@ -579,21 +593,27 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() { genBinExe := filepath.Join(genBinDir, env.Name) require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir") + s.setEnv(t, env) + buffer, logger := s.NewCapturingLogger() + logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) + err := InitializeCosmovisor(logger, []string{hwExe}) + require.NoError(t, err, "calling InitializeCosmovisor") + + genBinDirEval, err := filepath.EvalSymlinks(genBinDir) + require.NoError(t, err) + + genBinEvalExe := filepath.Join(genBinDirEval, env.Name) + expInLog := []string{ "checking on the genesis/bin directory", fmt.Sprintf("the %q directory already exists", genBinDir), "checking on the genesis/bin executable", fmt.Sprintf("copying executable into place: %q", genBinExe), fmt.Sprintf("making sure %q is executable", genBinExe), - fmt.Sprintf("the current symlink points to: %q", genBinExe), + fmt.Sprintf("the current symlink points to: %q", genBinEvalExe), fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)), } - s.setEnv(t, env) - buffer, logger := s.NewCapturingLogger() - logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name())) - err := InitializeCosmovisor(logger, []string{hwExe}) - require.NoError(t, err, "calling InitializeCosmovisor") bufferBz := buffer.Collect() bufferStr := string(bufferBz) for _, exp := range expInLog { @@ -693,7 +713,9 @@ func (s *InitTestSuite) TestInitializeCosmovisorWithOverrideCfg() { // read the config file cfgFile, err := os.Open(tc.cfg.DefaultCfgPath()) require.NoError(t, err) - defer cfgFile.Close() + defer func() { + _ = cfgFile.Close() + }() err = toml.NewDecoder(cfgFile).Decode(cfg) require.NoError(t, err) @@ -708,3 +730,14 @@ func (s *InitTestSuite) TestInitializeCosmovisorWithOverrideCfg() { }) } } + +func (s *InitTestSuite) prepareConfig(t *testing.T, config cosmovisorInitEnv) *cosmovisorInitEnv { + t.Helper() + + config.Home = s.T().TempDir() + + err := os.Chdir(config.Home) + require.NoError(t, err) + + return &config +} diff --git a/tools/cosmovisor/cmd/cosmovisor/root.go b/tools/cosmovisor/cmd/cosmovisor/root.go index 5cd31f8aea5b..de5e06d83e3e 100644 --- a/tools/cosmovisor/cmd/cosmovisor/root.go +++ b/tools/cosmovisor/cmd/cosmovisor/root.go @@ -14,7 +14,7 @@ func NewRootCmd() *cobra.Command { } rootCmd.AddCommand( - NewIntCmd(), + NewInitCmd(), runCmd, configCmd, NewVersionCmd(), diff --git a/tools/cosmovisor/cmd/cosmovisor/run.go b/tools/cosmovisor/cmd/cosmovisor/run.go index f835b59e5280..4da2ac24d846 100644 --- a/tools/cosmovisor/cmd/cosmovisor/run.go +++ b/tools/cosmovisor/cmd/cosmovisor/run.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "strings" "github.com/spf13/cobra" @@ -39,6 +40,12 @@ func run(cfgPath string, args []string, options ...RunOption) error { opt(&runCfg) } + // set current working directory to $DAEMON_NAME/cosmosvisor + // to allow current symlink to be relative + if err = os.Chdir(cfg.Root()); err != nil { + return err + } + logger := cfg.Logger(runCfg.StdOut) launcher, err := cosmovisor.NewLauncher(logger, cfg) if err != nil { diff --git a/tools/cosmovisor/process_test.go b/tools/cosmovisor/process_test.go index 988bcf0a8074..26ef82900fe6 100644 --- a/tools/cosmovisor/process_test.go +++ b/tools/cosmovisor/process_test.go @@ -1,5 +1,4 @@ -//go:build linux -// +build linux +//go:build linux || darwin package cosmovisor_test @@ -20,12 +19,26 @@ import ( upgradetypes "cosmossdk.io/x/upgrade/types" ) +var workDir string + +func init() { + workDir, _ = os.Getwd() +} + // TestLaunchProcess will try running the script a few times and watch upgrades work properly // and args are passed through 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} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + PollInterval: 15, + UnsafeSkipBackup: true, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor") // should run the genesis binary and produce expected output @@ -33,7 +46,11 @@ func TestLaunchProcess(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -51,7 +68,10 @@ func TestLaunchProcess(t *testing.T) { currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) args = []string{"second", "run", "--verbose"} stdout.Reset() stderr.Reset() @@ -63,14 +83,26 @@ func TestLaunchProcess(t *testing.T) { 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) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) } // TestPlanDisableRecase will test upgrades without lower case plan names 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} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/norecase"), + cosmovisor.Config{ + Name: "dummyd", + PollInterval: 20, + UnsafeSkipBackup: true, + DisableRecase: true, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor") // should run the genesis binary and produce expected output @@ -78,7 +110,11 @@ func TestPlanDisableRecase(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -96,7 +132,9 @@ func TestPlanDisableRecase(t *testing.T) { currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("Chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("Chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) args = []string{"second", "run", "--verbose"} stdout.Reset() stderr.Reset() @@ -108,13 +146,24 @@ func TestPlanDisableRecase(t *testing.T) { 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) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("Chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } 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} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + 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 @@ -122,7 +171,10 @@ func TestLaunchProcessWithRestartDelay(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -144,8 +196,17 @@ func TestLaunchProcessWithRestartDelay(t *testing.T) { // TestPlanShutdownGrace will test upgrades without lower case plan names 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} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/dontdie"), + cosmovisor.Config{ + Name: "dummyd", + 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 @@ -153,7 +214,9 @@ func TestPlanShutdownGrace(t *testing.T) { stdout, stderr := newBuffer(), newBuffer() currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -171,7 +234,9 @@ func TestPlanShutdownGrace(t *testing.T) { currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) args = []string{"second", "run", "--verbose"} stdout.Reset() stderr.Reset() @@ -183,7 +248,9 @@ func TestPlanShutdownGrace(t *testing.T) { 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) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } // TestLaunchProcess will try running the script a few times and watch upgrades work properly @@ -193,15 +260,26 @@ func TestLaunchProcessWithDownloads(t *testing.T) { // genesis -> chain2-zip_bin // 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} + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + PollInterval: 100, + UnsafeSkipBackup: true, + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmovisor") upgradeFilename := cfg.UpgradeInfoFilePath() // should run the genesis binary and produce expected output currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -216,7 +294,10 @@ func TestLaunchProcessWithDownloads(t *testing.T) { require.Equal(t, "Genesis autod. Args: some args "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary`+"\n", stdout.String()) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // start chain2 stdout.Reset() @@ -231,7 +312,9 @@ func TestLaunchProcessWithDownloads(t *testing.T) { require.True(t, doUpgrade) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // run the last chain args = []string{"end", "--halt", upgradeFilename} @@ -246,7 +329,9 @@ func TestLaunchProcessWithDownloads(t *testing.T) { // and this doesn't upgrade currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } // TestLaunchProcessWithDownloadsAndMissingPreupgrade will try running the script a few times and watch upgrades work properly @@ -256,22 +341,28 @@ func TestLaunchProcessWithDownloadsAndMissingPreupgrade(t *testing.T) { // genesis -> chain2-zip_bin // 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, - CustomPreUpgrade: "missing.sh", - } + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + PollInterval: 100, + UnsafeSkipBackup: true, + CustomPreUpgrade: "missing.sh", + }, + ) + logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmovisor") upgradeFilename := cfg.UpgradeInfoFilePath() // should run the genesis binary and produce expected output currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -292,15 +383,18 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { // genesis -> chain2-zip_bin // 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, - CustomPreUpgrade: "preupgrade.sh", - } + cfg := prepareConfig( + t, + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + PollInterval: 100, + UnsafeSkipBackup: true, + CustomPreUpgrade: "preupgrade.sh", + }, + ) + buf := newBuffer() // inspect output using buf.String() logger := log.NewLogger(buf).With(log.ModuleKey, "cosmovisor") upgradeFilename := cfg.UpgradeInfoFilePath() @@ -308,7 +402,9 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { // should run the genesis binary and produce expected output currentBin, err := cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) launcher, err := cosmovisor.NewLauncher(logger, cfg) require.NoError(t, err) @@ -323,10 +419,13 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { require.Equal(t, "Genesis autod. Args: some args "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary`+"\n", stdout.String()) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain2"), currentBin) + + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // should have preupgrade.sh results - require.FileExists(t, filepath.Join(home, "upgrade_name_chain2_height_49")) + require.FileExists(t, filepath.Join(cfg.Home, "upgrade_name_chain2_height_49")) // start chain2 stdout.Reset() @@ -341,10 +440,12 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { require.True(t, doUpgrade) currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) // should have preupgrade.sh results - require.FileExists(t, filepath.Join(home, "upgrade_name_chain3_height_936")) + require.FileExists(t, filepath.Join(cfg.Home, "upgrade_name_chain3_height_936")) // run the last chain args = []string{"end", "--halt", upgradeFilename} @@ -359,7 +460,9 @@ func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) { // and this doesn't upgrade currentBin, err = cfg.CurrentBin() require.NoError(t, err) - require.Equal(t, cfg.UpgradeBin("chain3"), currentBin) + rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3")) + require.NoError(t, err) + require.Equal(t, rPath, currentBin) } // TestSkipUpgrade tests heights that are identified to be skipped and return if upgrade height matches the skip heights diff --git a/tools/cosmovisor/upgrade_test.go b/tools/cosmovisor/upgrade_test.go index 5c8d867e2ca6..be79a0170f2e 100644 --- a/tools/cosmovisor/upgrade_test.go +++ b/tools/cosmovisor/upgrade_test.go @@ -1,5 +1,4 @@ -//go:build linux -// +build linux +//go:build darwin || linux package cosmovisor_test @@ -7,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "testing" @@ -28,13 +28,21 @@ func TestUpgradeTestSuite(t *testing.T) { } func (s *upgradeTestSuite) TestCurrentBin() { - home := copyTestData(s.T(), "validate") - cfg := cosmovisor.Config{Home: home, Name: "dummyd"} + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + }, + ) currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) // ensure we cannot set this to an invalid value for _, name := range []string{"missing", "nobin"} { @@ -43,7 +51,10 @@ func (s *upgradeTestSuite) TestCurrentBin() { currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin, name) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin, name) } // try a few times to make sure this can be reproduced @@ -55,29 +66,46 @@ func (s *upgradeTestSuite) TestCurrentBin() { currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin(name), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.UpgradeBin(name)) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) } } func (s *upgradeTestSuite) TestCurrentAlwaysSymlinkToDirectory() { - home := copyTestData(s.T(), "validate") - cfg := cosmovisor.Config{Home: home, Name: "dummyd"} + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + }, + ) currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) s.assertCurrentLink(cfg, "genesis") err = cfg.SetCurrentUpgrade(upgradetypes.Plan{Name: "chain2"}) s.Require().NoError(err) currentBin, err = cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.UpgradeBin("chain2"), currentBin) + + eval, err := filepath.EvalSymlinks(cfg.UpgradeBin("chain2")) + s.Require().NoError(err) + + s.Require().Equal(eval, currentBin) s.assertCurrentLink(cfg, filepath.Join("upgrades", "chain2")) } -func (s *upgradeTestSuite) assertCurrentLink(cfg cosmovisor.Config, target string) { +func (s *upgradeTestSuite) assertCurrentLink(cfg *cosmovisor.Config, target string) { link := filepath.Join(cfg.Root(), "current") + // ensure this is a symlink info, err := os.Lstat(link) s.Require().NoError(err) @@ -85,20 +113,29 @@ func (s *upgradeTestSuite) assertCurrentLink(cfg cosmovisor.Config, target strin dest, err := os.Readlink(link) s.Require().NoError(err) - expected := filepath.Join(cfg.Root(), target) - s.Require().Equal(expected, dest) + s.Require().Equal(target, dest) } // TODO: test with download (and test all download functions) func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() { - home := copyTestData(s.T(), "validate") - cfg := &cosmovisor.Config{Home: home, Name: "dummyd", AllowDownloadBinaries: true} + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/validate"), + cosmovisor.Config{ + Name: "dummyd", + AllowDownloadBinaries: true, + }, + ) + logger := log.NewLogger(os.Stdout).With(log.ModuleKey, "cosmovisor") currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin) + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) // do upgrade ignores bad files for _, name := range []string{"missing", "nobin"} { @@ -107,7 +144,11 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() { s.Require().Error(err, name) currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(cfg.GenesisBin(), currentBin, name) + + rPath, err := filepath.EvalSymlinks(cfg.GenesisBin()) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin, name) } // make sure it updates a few times @@ -121,7 +162,10 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() { currentBin, err := cfg.CurrentBin() s.Require().NoError(err) - s.Require().Equal(upgradeBin, currentBin) + rPath, err := filepath.EvalSymlinks(upgradeBin) + s.Require().NoError(err) + + s.Require().Equal(rPath, currentBin) } } @@ -135,26 +179,25 @@ func (s *upgradeTestSuite) TestUpgradeBinary() { }{ "get raw binary with checksum": { // sha256sum ./testdata/repo/raw_binary/autod - url: "./testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d", + url: workDir + "/testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d", canDownload: true, validBinary: true, }, "get raw binary with invalid checksum": { - url: "./testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", + url: workDir + "/testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", canDownload: false, }, "get zipped directory with valid checksum": { - // sha256sum ./testdata/repo/chain3-zip_dir/autod.zip - url: "./testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4", + url: workDir + "/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4", canDownload: true, validBinary: true, }, "get zipped directory with invalid checksum": { - url: "./testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", + url: workDir + "/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", canDownload: false, }, "invalid url": { - url: "./testdata/repo/bad_dir/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", + url: workDir + "/testdata/repo/bad_dir/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906", canDownload: false, }, "valid remote": { @@ -167,14 +210,14 @@ func (s *upgradeTestSuite) TestUpgradeBinary() { for label, tc := range cases { s.Run(label, func() { var err error - // make temp dir - home := copyTestData(s.T(), "download") - - cfg := &cosmovisor.Config{ - Home: home, - Name: "autod", - AllowDownloadBinaries: true, - } + cfg := prepareConfig( + s.T(), + fmt.Sprintf("%s/%s", workDir, "testdata/download"), + cosmovisor.Config{ + Name: "autod", + AllowDownloadBinaries: true, + }, + ) url := tc.url if strings.HasPrefix(url, "./") { @@ -198,17 +241,37 @@ func (s *upgradeTestSuite) TestUpgradeBinary() { } func (s *upgradeTestSuite) TestOsArch() { - // all download tests will fail if we are not on linux... - s.Require().Equal("linux/amd64", cosmovisor.OSArch()) + // all download tests will fail if we are not on linux or darwin... + hosts := []string{ + "linux/arm64", + "linux/amd64", + "darwin/amd64", + "darwin/arm64", + } + + s.Require().True(slices.Contains(hosts, cosmovisor.OSArch())) } // copyTestData will make a tempdir and then // "cp -r" a subdirectory under testdata there // returns the directory (which can now be used as Config.Home) and modified safely -func copyTestData(t *testing.T, subdir string) string { +func copyTestData(t *testing.T, testData string) string { t.Helper() tmpdir := t.TempDir() - require.NoError(t, copy.Copy(filepath.Join("testdata", subdir), tmpdir)) + require.NoError(t, copy.Copy(testData, tmpdir)) return tmpdir } + +func prepareConfig(t *testing.T, testData string, config cosmovisor.Config) *cosmovisor.Config { + t.Helper() + + tmpdir := copyTestData(t, testData) + + config.Home = tmpdir + + err := os.Chdir(config.Root()) + require.NoError(t, err) + + return &config +}