feat(cosmovisor): Add a restart delay after halt and before backup (#12188)
## Description Closes: #12101 --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules) - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [x] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
ce25dd2cc1
commit
f1196ad1a1
@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
### Features
|
||||
|
||||
* [\#12188](https://github.com/cosmos/cosmos-sdk/pull/12188) Add a `DAEMON_RESTART_DELAY` for allowing a node operator to define a delay between the node halt (for upgrade) and backup.
|
||||
* [\#11823](https://github.com/cosmos/cosmos-sdk/pull/11823) Refactor `cosmovisor` CLI to use `cobra`.
|
||||
* [\#11731](https://github.com/cosmos/cosmos-sdk/pull/11731) `cosmovisor version -o json` returns the cosmovisor version and the result of `simd --output json --long` in one JSON object.
|
||||
|
||||
@ -46,6 +47,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
* [\#12005](https://github.com/cosmos/cosmos-sdk/pull/12005) Fix cosmovisor binary usage for pre-upgrade
|
||||
|
||||
### CLI Breaking Changes
|
||||
|
||||
* [\#12188](https://github.com/cosmos/cosmos-sdk/pull/12188) Remove the possibility to set a time with only a number. `DAEMON_POLL_INTERVAL` env variable now only supports a duration (e.g. `100ms`, `30s`, `20m`).
|
||||
|
||||
## v1.1.0 2022-10-02
|
||||
|
||||
### Features
|
||||
|
||||
@ -74,7 +74,8 @@ All arguments passed to `cosmovisor run` will be passed to the application binar
|
||||
* `DAEMON_NAME` is the name of the binary itself (e.g. `gaiad`, `regend`, `simd`, etc.).
|
||||
* `DAEMON_ALLOW_DOWNLOAD_BINARIES` (*optional*), if set to `true`, will enable auto-downloading of new binaries (for security reasons, this is intended for full nodes rather than validators). By default, `cosmovisor` will not auto-download new binaries.
|
||||
* `DAEMON_RESTART_AFTER_UPGRADE` (*optional*, default = `true`), if `true`, restarts the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (`false`), `cosmovisor` stops running after an upgrade and requires the system administrator to manually restart it. Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs.
|
||||
* `DAEMON_POLL_INTERVAL` is the interval length for polling the upgrade plan file. The value can either be a number (in milliseconds) or a duration (e.g. `1s`). Default: 300 milliseconds.
|
||||
* `DAEMON_RESTART_DELAY` (*optional*, default none), allow a node operator to define a delay between the node halt (for upgrade) and backup by the specified time. The value must be a duration (e.g. `1s`).
|
||||
* `DAEMON_POLL_INTERVAL` (*optional*, default 300 milliseconds), is the interval length for polling the upgrade plan file. The value must be a duration (e.g. `1s`).
|
||||
* `DAEMON_BACKUP_DIR` option to set a custom backup directory. If not set, `DAEMON_HOME` is used.
|
||||
* `UNSAFE_SKIP_BACKUP` (defaults to `false`), if set to `true`, upgrades directly without performing a backup. Otherwise (`false`, default) backs up the data before trying the upgrade. The default value of false is useful and recommended in case of failures and when a backup needed to rollback. We recommend using the default backup option `UNSAFE_SKIP_BACKUP=false`.
|
||||
* `DAEMON_PREUPGRADE_MAX_RETRIES` (defaults to `0`). The maximum number of times to call `pre-upgrade` in the application after exit status of `31`. After the maximum number of retries, cosmovisor fails the upgrade.
|
||||
|
||||
@ -2,7 +2,6 @@ package cosmovisor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -23,6 +22,7 @@ const (
|
||||
EnvName = "DAEMON_NAME"
|
||||
EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES"
|
||||
EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE"
|
||||
EnvRestartDelay = "DAEMON_RESTART_DELAY"
|
||||
EnvSkipBackup = "UNSAFE_SKIP_BACKUP"
|
||||
EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR"
|
||||
EnvInterval = "DAEMON_POLL_INTERVAL"
|
||||
@ -45,6 +45,7 @@ type Config struct {
|
||||
Name string
|
||||
AllowDownloadBinaries bool
|
||||
RestartAfterUpgrade bool
|
||||
RestartDelay time.Duration
|
||||
PollInterval time.Duration
|
||||
UnsafeSkipBackup bool
|
||||
DataBackupPath string
|
||||
@ -97,6 +98,13 @@ func (cfg *Config) SymLinkToGenesis() (string, error) {
|
||||
return cfg.GenesisBin(), nil
|
||||
}
|
||||
|
||||
// WaitRestartDelay will block and wait until the RestartDelay has elapsed.
|
||||
func (cfg *Config) WaitRestartDelay() {
|
||||
if cfg.RestartDelay > 0 {
|
||||
time.Sleep(cfg.RestartDelay)
|
||||
}
|
||||
}
|
||||
|
||||
// CurrentBin is the path to the currently selected binary (genesis if no link is set)
|
||||
// This will resolve the symlink to the underlying directory to make it easier to debug
|
||||
func (cfg *Config) CurrentBin() (string, error) {
|
||||
@ -152,23 +160,27 @@ func GetConfigFromEnv() (*Config, error) {
|
||||
|
||||
interval := os.Getenv(EnvInterval)
|
||||
if interval != "" {
|
||||
var intervalUInt uint64
|
||||
intervalUInt, err = strconv.ParseUint(interval, 10, 32)
|
||||
if err == nil {
|
||||
cfg.PollInterval = time.Millisecond * time.Duration(intervalUInt)
|
||||
val, err := parseEnvDuration(interval)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid: %s: %w", EnvInterval, err))
|
||||
} else {
|
||||
cfg.PollInterval, err = time.ParseDuration(interval)
|
||||
}
|
||||
switch {
|
||||
case err != nil:
|
||||
errs = append(errs, fmt.Errorf("invalid %s: could not parse \"%s\" into either a duration or uint (milliseconds)", EnvInterval, interval))
|
||||
case cfg.PollInterval <= 0:
|
||||
errs = append(errs, fmt.Errorf("invalid %s: must be greater than 0", EnvInterval))
|
||||
cfg.PollInterval = val
|
||||
}
|
||||
} else {
|
||||
cfg.PollInterval = 300 * time.Millisecond
|
||||
}
|
||||
|
||||
cfg.RestartDelay = 0 // default value but makes it explicit
|
||||
restartDelay := os.Getenv(EnvRestartDelay)
|
||||
if restartDelay != "" {
|
||||
val, err := parseEnvDuration(restartDelay)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("invalid: %s: %w", EnvRestartDelay, err))
|
||||
} else {
|
||||
cfg.RestartDelay = val
|
||||
}
|
||||
}
|
||||
|
||||
envPreupgradeMaxRetriesVal := os.Getenv(EnvPreupgradeMaxRetries)
|
||||
if cfg.PreupgradeMaxRetries, err = strconv.Atoi(envPreupgradeMaxRetriesVal); err != nil && envPreupgradeMaxRetriesVal != "" {
|
||||
errs = append(errs, fmt.Errorf("%s could not be parsed to int: %w", EnvPreupgradeMaxRetries, err))
|
||||
@ -182,6 +194,19 @@ func GetConfigFromEnv() (*Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func parseEnvDuration(input string) (time.Duration, error) {
|
||||
duration, err := time.ParseDuration(input)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not parse '%s' into a duration: %w", input, err)
|
||||
}
|
||||
|
||||
if duration <= 0 {
|
||||
return 0, fmt.Errorf("must be greater than 0")
|
||||
}
|
||||
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
// LogConfigOrError logs either the config details or the error.
|
||||
func LogConfigOrError(logger *zerolog.Logger, cfg *Config, err error) {
|
||||
if cfg == nil && err == nil {
|
||||
@ -201,15 +226,18 @@ func LogConfigOrError(logger *zerolog.Logger, cfg *Config, err error) {
|
||||
// and that Name is set
|
||||
func (cfg *Config) validate() []error {
|
||||
var errs []error
|
||||
|
||||
// validate EnvName
|
||||
if cfg.Name == "" {
|
||||
errs = append(errs, errors.New(EnvName+" is not set"))
|
||||
errs = append(errs, fmt.Errorf("%s is not set", EnvName))
|
||||
}
|
||||
|
||||
// validate EnvHome
|
||||
switch {
|
||||
case cfg.Home == "":
|
||||
errs = append(errs, errors.New(EnvHome+" is not set"))
|
||||
errs = append(errs, fmt.Errorf("%s is not set", EnvHome))
|
||||
case !filepath.IsAbs(cfg.Home):
|
||||
errs = append(errs, errors.New(EnvHome+" must be an absolute path"))
|
||||
errs = append(errs, fmt.Errorf("%s must be an absolute path", EnvHome))
|
||||
default:
|
||||
switch info, err := os.Stat(cfg.Root()); {
|
||||
case err != nil:
|
||||
@ -223,7 +251,8 @@ func (cfg *Config) validate() []error {
|
||||
if cfg.UnsafeSkipBackup == true {
|
||||
return errs
|
||||
}
|
||||
// if UnsafeSkipBackup is false, check if the DataBackupPath valid
|
||||
|
||||
// if UnsafeSkipBackup is false, validate DataBackupPath
|
||||
switch {
|
||||
case cfg.DataBackupPath == "":
|
||||
errs = append(errs, fmt.Errorf("%s must not be empty", EnvDataBackupPath))
|
||||
@ -327,11 +356,13 @@ func (cfg Config) DetailString() string {
|
||||
{EnvName, cfg.Name},
|
||||
{EnvDownloadBin, fmt.Sprintf("%t", cfg.AllowDownloadBinaries)},
|
||||
{EnvRestartUpgrade, fmt.Sprintf("%t", cfg.RestartAfterUpgrade)},
|
||||
{EnvRestartDelay, fmt.Sprintf("%s", cfg.RestartDelay)},
|
||||
{EnvInterval, fmt.Sprintf("%s", cfg.PollInterval)},
|
||||
{EnvSkipBackup, fmt.Sprintf("%t", cfg.UnsafeSkipBackup)},
|
||||
{EnvDataBackupPath, cfg.DataBackupPath},
|
||||
{EnvPreupgradeMaxRetries, fmt.Sprintf("%d", cfg.PreupgradeMaxRetries)},
|
||||
}
|
||||
|
||||
derivedEntries := []struct{ name, value string }{
|
||||
{"Root Dir", cfg.Root()},
|
||||
{"Upgrade Dir", cfg.BaseUpgradeDir()},
|
||||
|
||||
@ -31,6 +31,7 @@ type cosmovisorEnv struct {
|
||||
Name string
|
||||
DownloadBin string
|
||||
RestartUpgrade string
|
||||
RestartDelay string
|
||||
SkipBackup string
|
||||
DataBackupPath string
|
||||
Interval string
|
||||
@ -44,6 +45,7 @@ func (c cosmovisorEnv) ToMap() map[string]string {
|
||||
EnvName: c.Name,
|
||||
EnvDownloadBin: c.DownloadBin,
|
||||
EnvRestartUpgrade: c.RestartUpgrade,
|
||||
EnvRestartDelay: c.RestartDelay,
|
||||
EnvSkipBackup: c.SkipBackup,
|
||||
EnvDataBackupPath: c.DataBackupPath,
|
||||
EnvInterval: c.Interval,
|
||||
@ -62,6 +64,8 @@ func (c *cosmovisorEnv) Set(envVar, envVal string) {
|
||||
c.DownloadBin = envVal
|
||||
case EnvRestartUpgrade:
|
||||
c.RestartUpgrade = envVal
|
||||
case EnvRestartDelay:
|
||||
c.RestartDelay = envVal
|
||||
case EnvSkipBackup:
|
||||
c.SkipBackup = envVal
|
||||
case EnvDataBackupPath:
|
||||
@ -354,12 +358,13 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
|
||||
absPath, perr := filepath.Abs(relPath)
|
||||
s.Require().NoError(perr)
|
||||
|
||||
newConfig := func(home, name, dataBackupPath string, downloadBin, restartUpgrade, skipBackup bool, interval, preupgradeMaxRetries int) *Config {
|
||||
newConfig := func(home, name string, downloadBin, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval int, preupgradeMaxRetries int) *Config {
|
||||
return &Config{
|
||||
Home: home,
|
||||
Name: name,
|
||||
AllowDownloadBinaries: downloadBin,
|
||||
RestartAfterUpgrade: restartUpgrade,
|
||||
RestartDelay: time.Millisecond * time.Duration(restartDelay),
|
||||
PollInterval: time.Millisecond * time.Duration(interval),
|
||||
UnsafeSkipBackup: skipBackup,
|
||||
DataBackupPath: dataBackupPath,
|
||||
@ -373,160 +378,201 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
|
||||
expectedCfg *Config
|
||||
expectedErrCount int
|
||||
}{
|
||||
// EnvHome, EnvName, EnvDownloadBin, EnvRestartUpgrade, EnvSkipBackup, EnvDataBackupPath, EnvDataBackupPath, EnvInterval, EnvPreupgradeMaxRetries
|
||||
{
|
||||
name: "all bad",
|
||||
envVals: cosmovisorEnv{"", "", "bad", "bad", "bad", "", "bad", "bad"},
|
||||
name: "all bad",
|
||||
envVals: cosmovisorEnv{
|
||||
Home: "",
|
||||
Name: "",
|
||||
DownloadBin: "bad",
|
||||
RestartUpgrade: "bad",
|
||||
RestartDelay: "bad",
|
||||
SkipBackup: "bad",
|
||||
DataBackupPath: "bad",
|
||||
Interval: "bad",
|
||||
PreupgradeMaxRetries: "bad",
|
||||
},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 8,
|
||||
expectedErrCount: 9,
|
||||
},
|
||||
{
|
||||
name: "all good",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, false, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "nothing set",
|
||||
envVals: cosmovisorEnv{"", "", "", "", "", "", "", ""},
|
||||
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", ""},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 3,
|
||||
},
|
||||
// Note: Home and Name tests are done in TestValidate
|
||||
{
|
||||
name: "download bin bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "bad", "false", "true", "", "303", "1"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "bad", "false", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "download bin not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "", "false", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "", "false", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "download bin true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, false, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "download bin false",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
// EnvHome, EnvName, EnvDownloadBin, EnvRestartUpgrade, EnvSkipBackup, EnvDataBackupPath, EnvInterval, EnvPreupgradeMaxRetries
|
||||
{
|
||||
name: "restart upgrade bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "bad", "true", "", "303", "1"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "bad", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart upgrade not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, true, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart upgrade true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, true, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart upgrade true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, false, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
// EnvHome, EnvName, EnvDownloadBin, EnvRestartUpgrade, EnvSkipBackup, EnvDataBackupPath, EnvInterval, EnvPreupgradeMaxRetries
|
||||
{
|
||||
name: "skip unsafe backups bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "bad", "", "303", "1"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "bad", "", "303ms", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "skip unsafe backups not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, false, false, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "skip unsafe backups true",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "true", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, false, true, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "skip unsafe backups false",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "", "303", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, true, false, false, 303, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "false", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
// EnvHome, EnvName, EnvDownloadBin, EnvRestartUpgrade, EnvSkipBackup, EnvDataBackupPath, EnvInterval, EnvPreupgradeMaxRetries
|
||||
{
|
||||
name: "poll interval bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "bad", "1"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "bad", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "poll interval 0",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "0", "1"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "0", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "poll interval not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, false, 300, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 300, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "poll interval 987",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "987", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, false, 987, 1),
|
||||
expectedErrCount: 0,
|
||||
name: "poll interval 600",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "600", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "poll interval 1s",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "1s", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, false, 1000, 1),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "1s", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 1000, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "poll interval -3m",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "-3m", "1"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "-3m", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "bad", "false", "", "303ms", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay 0",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "0", "false", "", "303ms", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "", "false", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 0, false, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart delay 600",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600", "false", "", "300ms", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "restart delay 1s",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "1s", "false", "", "303ms", "1"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 1000, false, absPath, 303, 1),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "restart delay -3m",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "-3m", "false", "", "303ms", "1"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
// EnvHome, EnvName, EnvDownloadBin, EnvRestartUpgrade, EnvSkipBackup, EnvDataBackupPath, EnvInterval, EnvPreupgradeMaxRetries
|
||||
{
|
||||
name: "prepupgrade max retries bad",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "406", "bad"},
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "bad"},
|
||||
expectedCfg: nil,
|
||||
expectedErrCount: 1,
|
||||
},
|
||||
{
|
||||
name: "prepupgrade max retries 0",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "406", "0"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, false, 406, 0),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "0"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "prepupgrade max retries not set",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "406", ""},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, false, 406, 0),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", ""},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
{
|
||||
name: "prepupgrade max retries 5",
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "false", "", "406", "5"},
|
||||
expectedCfg: newConfig(absPath, "testname", absPath, false, false, false, 406, 5),
|
||||
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "5"},
|
||||
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 5),
|
||||
expectedErrCount: 0,
|
||||
},
|
||||
}
|
||||
|
||||
@ -69,6 +69,8 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
|
||||
}
|
||||
|
||||
if !IsSkipUpgradeHeight(args, l.fw.currentInfo) {
|
||||
l.cfg.WaitRestartDelay()
|
||||
|
||||
if err := l.doBackup(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ package cosmovisor_test
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -47,11 +48,9 @@ func (s *processTestSuite) TestLaunchProcess() {
|
||||
require.NoError(err)
|
||||
require.True(doUpgrade)
|
||||
require.Equal("", stderr.String())
|
||||
require.Equal(fmt.Sprintf("Genesis foo bar 1234 %s\nUPGRADE \"chain2\" NEEDED at height: 49: {}\n", upgradeFile),
|
||||
stdout.String())
|
||||
require.Equal(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(err)
|
||||
|
||||
@ -70,6 +69,37 @@ func (s *processTestSuite) TestLaunchProcess() {
|
||||
require.Equal(cfg.UpgradeBin("chain2"), currentBin)
|
||||
}
|
||||
|
||||
func (s *processTestSuite) TestLaunchProcessWithRestartDelay() {
|
||||
// binaries from testdata/validate directory
|
||||
require := s.Require()
|
||||
home := copyTestData(s.T(), "validate")
|
||||
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", RestartDelay: 5 * time.Second, PollInterval: 20, UnsafeSkipBackup: true}
|
||||
logger := cosmovisor.NewLogger()
|
||||
|
||||
// should run the genesis binary and produce expected output
|
||||
stdout, stderr := NewBuffer(), NewBuffer()
|
||||
currentBin, err := cfg.CurrentBin()
|
||||
require.NoError(err)
|
||||
require.Equal(cfg.GenesisBin(), currentBin)
|
||||
|
||||
launcher, err := cosmovisor.NewLauncher(logger, cfg)
|
||||
require.NoError(err)
|
||||
|
||||
upgradeFile := cfg.UpgradeInfoFilePath()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
doUpgrade, err := launcher.Run([]string{"foo", "bar", "1234", upgradeFile}, stdout, stderr)
|
||||
require.NoError(err)
|
||||
require.True(doUpgrade)
|
||||
|
||||
// may not be the best way but the fastest way to check we meet the delay
|
||||
// in addition to comparing both the runtime of this test and TestLaunchProcess in addition
|
||||
if time.Since(start) < cfg.RestartDelay {
|
||||
require.FailNow("restart delay not met")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLaunchProcess will try running the script a few times and watch upgrades work properly
|
||||
// and args are passed through
|
||||
func (s *processTestSuite) TestLaunchProcessWithDownloads() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user