feat(cosmovisor): extend add upgrade to support non governance upgrade (#16549)
This commit is contained in:
parent
9b237c7189
commit
c6d5b7c050
@ -38,10 +38,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
## Features
|
||||
|
||||
* [#16413](https://github.com/cosmos/cosmos-sdk/issues/16413) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
|
||||
* [#16573](https://github.com/cosmos/cosmos-sdk/pull/16573) Extend `cosmovisor` configuration with new log format options
|
||||
* [#16550](https://github.com/cosmos/cosmos-sdk/pull/16550) Add COSMOVISOR_CUSTOM_PREUPGRADE to cosmovisor to execute custom pre-upgrade scripts (separate from daemon pre-upgrade).
|
||||
* [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor.
|
||||
* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
|
||||
|
||||
## Improvements
|
||||
|
||||
|
||||
@ -52,10 +52,10 @@ To install the latest version of `cosmovisor`, run the following command:
|
||||
go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
|
||||
```
|
||||
|
||||
To install a previous version, you can specify the version. IMPORTANT: Chains that use Cosmos SDK v0.44.3 or earlier (eg v0.44.2) and want to use auto-download feature MUST use `cosmovisor v0.1.0`
|
||||
To install a previous version, you can specify the version:
|
||||
|
||||
```shell
|
||||
go install github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor@v0.1.0
|
||||
go install github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor@v1.5.0
|
||||
```
|
||||
|
||||
Run `cosmovisor version` to check the cosmovisor version.
|
||||
@ -367,7 +367,7 @@ Open a new terminal window and submit an upgrade proposal along with a deposit a
|
||||
**>= v0.50+**:
|
||||
|
||||
```shell
|
||||
./build/simd tx upgrade software-upgrade test1 --title upgrade --summary upgrade --upgrade-height 200 --from validator --yes
|
||||
./build/simd tx upgrade software-upgrade test1 --title upgrade --summary upgrade --upgrade-height 200 --upgrade-info "{}" --no-validate --from validator --yes
|
||||
./build/simd tx gov deposit 1 10000000stake --from validator --yes
|
||||
./build/simd tx gov vote 1 yes --from validator --yes
|
||||
```
|
||||
|
||||
@ -12,8 +12,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/x/upgrade/plan"
|
||||
upgradetypes "cosmossdk.io/x/upgrade/types"
|
||||
@ -44,9 +42,6 @@ const (
|
||||
currentLink = "current"
|
||||
)
|
||||
|
||||
// must be the same as x/upgrade/types.UpgradeInfoFilename
|
||||
const defaultFilename = "upgrade-info.json"
|
||||
|
||||
// Config is the information passed in to control the daemon
|
||||
type Config struct {
|
||||
Home string
|
||||
@ -96,7 +91,7 @@ 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", defaultFilename)
|
||||
return filepath.Join(cfg.Home, "data", upgradetypes.UpgradeInfoFilename)
|
||||
}
|
||||
|
||||
// SymLinkToGenesis creates a symbolic link from "./current" to the genesis directory.
|
||||
@ -224,7 +219,7 @@ func (cfg *Config) Logger(dst io.Writer) log.Logger {
|
||||
var logger log.Logger
|
||||
|
||||
if cfg.DisableLogs {
|
||||
logger = log.NewCustomLogger(zerolog.Nop())
|
||||
logger = log.NewNopLogger()
|
||||
} else {
|
||||
logger = log.NewLogger(dst,
|
||||
log.ColorOption(cfg.ColorLogs),
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"cosmossdk.io/tools/cosmovisor"
|
||||
upgradetypes "cosmossdk.io/x/upgrade/types"
|
||||
)
|
||||
|
||||
func NewAddUpgradeCmd() *cobra.Command {
|
||||
@ -19,7 +22,8 @@ func NewAddUpgradeCmd() *cobra.Command {
|
||||
RunE: AddUpgrade,
|
||||
}
|
||||
|
||||
addUpgrade.Flags().Bool(cosmovisor.FlagForce, false, "overwrite existing upgrade binary")
|
||||
addUpgrade.Flags().Bool(cosmovisor.FlagForce, false, "overwrite existing upgrade binary / upgrade-info.json file")
|
||||
addUpgrade.Flags().Int64(cosmovisor.FlagUpgradeHeight, 0, "define a height at which to upgrade the binary automatically (without governance proposal)")
|
||||
|
||||
return addUpgrade
|
||||
}
|
||||
@ -33,7 +37,7 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
|
||||
|
||||
logger := cfg.Logger(os.Stdout)
|
||||
|
||||
upgradeName := args[0]
|
||||
upgradeName := strings.ToLower(args[0])
|
||||
if len(upgradeName) == 0 {
|
||||
return fmt.Errorf("upgrade name cannot be empty")
|
||||
}
|
||||
@ -59,22 +63,55 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("failed to read binary: %w", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(cfg.UpgradeBin(upgradeName)); err == nil {
|
||||
if force, _ := cmd.Flags().GetBool(cosmovisor.FlagForce); !force {
|
||||
return fmt.Errorf("upgrade binary already exists at %s", cfg.UpgradeBin(upgradeName))
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("Overwriting %s for %s upgrade", executablePath, upgradeName))
|
||||
} else if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to check if upgrade binary exists: %w", err)
|
||||
force, err := cmd.Flags().GetBool(cosmovisor.FlagForce)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get force flag: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(cfg.UpgradeBin(upgradeName), executableData, 0o600); err != nil {
|
||||
return fmt.Errorf("failed to write binary to location: %w", err)
|
||||
if err := saveOrAbort(cfg.UpgradeBin(upgradeName), executableData, force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("Using %s for %s upgrade", executablePath, upgradeName))
|
||||
logger.Info(fmt.Sprintf("Upgrade binary located at %s", cfg.UpgradeBin(upgradeName)))
|
||||
|
||||
if upgradeHeight, err := cmd.Flags().GetInt64(cosmovisor.FlagUpgradeHeight); err != nil {
|
||||
return fmt.Errorf("failed to get upgrade-height flag: %w", err)
|
||||
} else if upgradeHeight > 0 {
|
||||
plan := upgradetypes.Plan{Name: upgradeName, Height: upgradeHeight}
|
||||
if err := plan.ValidateBasic(); err != nil {
|
||||
panic(fmt.Errorf("something is wrong with cosmovisor: %w", err))
|
||||
}
|
||||
|
||||
// create upgrade-info.json file
|
||||
planData, err := json.Marshal(plan)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal upgrade plan: %w", err)
|
||||
}
|
||||
|
||||
if err := saveOrAbort(cfg.UpgradeInfoFilePath(), planData, force); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("%s created, %s upgrade binary will switch at height %d", upgradetypes.UpgradeInfoFilename, upgradeName, upgradeHeight))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveOrAbort saves data to path or aborts if file exists and force is false
|
||||
func saveOrAbort(path string, data []byte, force bool) error {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
if !force {
|
||||
return fmt.Errorf("file already exists at %s", path)
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to check if file exists: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, 0o600); err != nil {
|
||||
return fmt.Errorf("failed to write binary to location: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -16,9 +16,10 @@ import (
|
||||
)
|
||||
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init <path to executable>",
|
||||
Short: "Initialize a cosmovisor daemon home directory.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Use: "init <path to executable>",
|
||||
Short: "Initialize a cosmovisor daemon home directory.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return InitializeCosmovisor(nil, args)
|
||||
},
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -244,8 +243,7 @@ func (p *BufferedPipe) panicIfStarted(msg string) {
|
||||
func (s *InitTestSuite) NewCapturingLogger() (*BufferedPipe, log.Logger) {
|
||||
bufferedStdOut, err := StartNewBufferedPipe("stdout", os.Stdout)
|
||||
s.Require().NoError(err, "creating stdout buffered pipe")
|
||||
output := zerolog.ConsoleWriter{Out: bufferedStdOut, TimeFormat: time.RFC3339Nano}
|
||||
logger := log.NewCustomLogger(zerolog.New(output).With().Str("module", "cosmovisor").Timestamp().Logger())
|
||||
logger := log.NewLogger(bufferedStdOut, log.ColorOption(false), log.TimeFormatOption(time.RFC3339Nano)).With(log.ModuleKey, "cosmovisor")
|
||||
return &bufferedStdOut, logger
|
||||
}
|
||||
|
||||
|
||||
@ -13,10 +13,11 @@ import (
|
||||
|
||||
func NewVersionCmd() *cobra.Command {
|
||||
versionCmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display cosmovisor and APP version.",
|
||||
Use: "version",
|
||||
Short: "Display cosmovisor and APP version.",
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
noAppVersion, _ := cmd.Flags().GetBool(cosmovisor.FlagNoAppVersion)
|
||||
noAppVersion, _ := cmd.Flags().GetBool(cosmovisor.FlagCosmovisorOnly)
|
||||
if val, err := cmd.Flags().GetString(cosmovisor.FlagOutput); val == "json" && err == nil {
|
||||
return printVersionJSON(cmd, args, noAppVersion)
|
||||
}
|
||||
@ -26,7 +27,7 @@ func NewVersionCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
versionCmd.Flags().StringP(cosmovisor.FlagOutput, "o", "text", "Output format (text|json)")
|
||||
versionCmd.Flags().Bool(cosmovisor.FlagNoAppVersion, false, "Don't print APP version")
|
||||
versionCmd.Flags().Bool(cosmovisor.FlagCosmovisorOnly, false, "Print cosmovisor version only")
|
||||
|
||||
return versionCmd
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package cosmovisor
|
||||
const (
|
||||
FlagOutput = "output"
|
||||
FlagSkipUpgradeHeight = "unsafe-skip-upgrades"
|
||||
FlagNoAppVersion = "no-app-version"
|
||||
FlagCosmovisorOnly = "cosmovisor-only"
|
||||
FlagForce = "force"
|
||||
FlagUpgradeHeight = "upgrade-height"
|
||||
)
|
||||
|
||||
@ -6,7 +6,6 @@ require (
|
||||
cosmossdk.io/log v1.1.0
|
||||
cosmossdk.io/x/upgrade v0.0.0-20230614103911-b3da8bb4e801
|
||||
github.com/otiai10/copy v1.12.0
|
||||
github.com/rs/zerolog v1.29.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
)
|
||||
@ -133,6 +132,7 @@ require (
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/rs/cors v1.8.3 // indirect
|
||||
github.com/rs/zerolog v1.29.1 // indirect
|
||||
github.com/sasha-s/go-deadlock v0.3.1 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/otiai10/copy"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/x/upgrade/plan"
|
||||
@ -22,19 +21,18 @@ import (
|
||||
)
|
||||
|
||||
type Launcher struct {
|
||||
logger *zerolog.Logger
|
||||
logger log.Logger
|
||||
cfg *Config
|
||||
fw *fileWatcher
|
||||
}
|
||||
|
||||
func NewLauncher(logger log.Logger, cfg *Config) (Launcher, error) {
|
||||
fw, err := newUpgradeFileWatcher(logger, cfg.UpgradeInfoFilePath(), cfg.PollInterval)
|
||||
fw, err := newUpgradeFileWatcher(cfg, logger)
|
||||
if err != nil {
|
||||
return Launcher{}, err
|
||||
}
|
||||
|
||||
zl := logger.Impl().(*zerolog.Logger)
|
||||
return Launcher{logger: zl, cfg: cfg, fw: fw}, nil
|
||||
return Launcher{logger: logger, cfg: cfg, fw: fw}, nil
|
||||
}
|
||||
|
||||
// Run launches the app in a subprocess and returns when the subprocess (app)
|
||||
@ -50,7 +48,7 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
|
||||
return false, fmt.Errorf("current binary is invalid: %w", err)
|
||||
}
|
||||
|
||||
l.logger.Info().Str("path", bin).Strs("args", args).Msg("running app")
|
||||
l.logger.Info("running app", "path", bin, "args", args)
|
||||
cmd := exec.Command(bin, args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
@ -63,7 +61,8 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
if err := cmd.Process.Signal(sig); err != nil {
|
||||
l.logger.Fatal().Err(err).Str("bin", bin).Msg("terminated")
|
||||
l.logger.Error("terminated", "error", err, "bin", bin)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -82,7 +81,7 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := UpgradeBinary(log.NewCustomLogger(*l.logger), l.cfg, l.fw.currentInfo); err != nil {
|
||||
if err := UpgradeBinary(l.logger, l.cfg, l.fw.currentInfo); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@ -100,13 +99,14 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
|
||||
// When it returns, the process (app) is finished.
|
||||
//
|
||||
// It returns (true, nil) if an upgrade should be initiated (and we killed the process)
|
||||
// It returns (false, err) if the process died by itself, or there was an issue reading the upgrade-info file.
|
||||
// It returns (false, err) if the process died by itself
|
||||
// It returns (false, nil) if the process exited normally without triggering an upgrade. This is very unlikely
|
||||
// to happened with "start" but may happened with short-lived commands like `gaiad export ...`
|
||||
// to happen with "start" but may happen with short-lived commands like `simd export ...`
|
||||
func (l Launcher) WaitForUpgradeOrExit(cmd *exec.Cmd) (bool, error) {
|
||||
currentUpgrade, err := l.cfg.UpgradeInfo()
|
||||
if err != nil {
|
||||
l.logger.Error().Err(err)
|
||||
// upgrade info not found do nothing
|
||||
currentUpgrade = upgradetypes.Plan{}
|
||||
}
|
||||
|
||||
cmdDone := make(chan error)
|
||||
@ -117,7 +117,7 @@ func (l Launcher) WaitForUpgradeOrExit(cmd *exec.Cmd) (bool, error) {
|
||||
select {
|
||||
case <-l.fw.MonitorUpdate(currentUpgrade):
|
||||
// upgrade - kill the process and restart
|
||||
l.logger.Info().Msg("daemon shutting down in an attempt to restart")
|
||||
l.logger.Info("daemon shutting down in an attempt to restart")
|
||||
_ = cmd.Process.Kill()
|
||||
case err := <-cmdDone:
|
||||
l.fw.Stop()
|
||||
@ -139,7 +139,7 @@ func (l Launcher) doBackup() error {
|
||||
if !l.cfg.UnsafeSkipBackup {
|
||||
// check if upgrade-info.json is not empty.
|
||||
var uInfo upgradetypes.Plan
|
||||
upgradeInfoFile, err := os.ReadFile(filepath.Join(l.cfg.Home, "data", "upgrade-info.json"))
|
||||
upgradeInfoFile, err := os.ReadFile(l.cfg.UpgradeInfoFilePath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading upgrade-info.json: %w", err)
|
||||
}
|
||||
@ -157,7 +157,7 @@ func (l Launcher) doBackup() error {
|
||||
stStr := fmt.Sprintf("%d-%d-%d", st.Year(), st.Month(), st.Day())
|
||||
dst := filepath.Join(l.cfg.DataBackupPath, fmt.Sprintf("data"+"-backup-%s", stStr))
|
||||
|
||||
l.logger.Info().Time("backup start time", st).Msg("starting to take backup of data directory")
|
||||
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 {
|
||||
@ -166,38 +166,39 @@ func (l Launcher) doBackup() error {
|
||||
|
||||
// backup is done, lets check endtime to calculate total time taken for backup process
|
||||
et := time.Now()
|
||||
l.logger.Info().Str("backup saved at", dst).Time("backup completion time", et).TimeDiff("time taken to complete backup", et, st).Msg("backup completed")
|
||||
l.logger.Info("backup completed", "backup saved at", dst, "backup completion time", et, "time taken to complete backup", et.Sub(st))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doCustomPreUpgrade executes the custom preupgrade script if provided.
|
||||
func (l Launcher) doCustomPreUpgrade() error {
|
||||
if l.cfg.CustomPreupgrade == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if upgrade-info.json is not empty.
|
||||
var uInfo upgradetypes.Plan
|
||||
var upgradePlan upgradetypes.Plan
|
||||
upgradeInfoFile, err := os.ReadFile(l.cfg.UpgradeInfoFilePath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading upgrade-info.json: %w", err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(upgradeInfoFile, &uInfo); err != nil {
|
||||
if err = json.Unmarshal(upgradeInfoFile, &upgradePlan); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = uInfo.ValidateBasic(); err != nil {
|
||||
if err = upgradePlan.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("invalid upgrade plan: %w", err)
|
||||
}
|
||||
|
||||
// check if preupgradeFile is executable file
|
||||
preupgradeFile := filepath.Join(l.cfg.Home, "cosmovisor", l.cfg.CustomPreupgrade)
|
||||
l.logger.Info().Str("Looking for COSMOVISOR_CUSTOM_PREUPGRADE file ", preupgradeFile)
|
||||
l.logger.Info("looking for COSMOVISOR_CUSTOM_PREUPGRADE file", "file", preupgradeFile)
|
||||
info, err := os.Stat(preupgradeFile)
|
||||
if err != nil {
|
||||
l.logger.Error().Str("file", preupgradeFile).Msg("COSMOVISOR_CUSTOM_PREUPGRADE file missing")
|
||||
l.logger.Error("COSMOVISOR_CUSTOM_PREUPGRADE file missing", "file", preupgradeFile)
|
||||
return err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
@ -212,20 +213,20 @@ func (l Launcher) doCustomPreUpgrade() error {
|
||||
newMode := oldMode | 0o100
|
||||
if oldMode != newMode {
|
||||
if err := os.Chmod(preupgradeFile, newMode); err != nil {
|
||||
l.logger.Info().Msg("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
|
||||
l.logger.Info("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
|
||||
return fmt.Errorf("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
|
||||
}
|
||||
}
|
||||
|
||||
// Run preupgradeFile
|
||||
cmd := exec.Command(preupgradeFile, uInfo.Name, fmt.Sprintf("%d", uInfo.Height))
|
||||
cmd := exec.Command(preupgradeFile, upgradePlan.Name, fmt.Sprintf("%d", upgradePlan.Height))
|
||||
cmd.Dir = l.cfg.Home
|
||||
result, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.logger.Info().Str("command", preupgradeFile).Str("argv1", uInfo.Name).Str("argv2", fmt.Sprintf("%d", uInfo.Height)).Bytes("result", result).Msg("COSMOVISOR_CUSTOM_PREUPGRADE result")
|
||||
l.logger.Info("COSMOVISOR_CUSTOM_PREUPGRADE result", "command", preupgradeFile, "argv1", upgradePlan.Name, "argv2", fmt.Sprintf("%d", upgradePlan.Height), "result", result)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -245,17 +246,17 @@ func (l *Launcher) doPreUpgrade() error {
|
||||
|
||||
switch err.(*exec.ExitError).ProcessState.ExitCode() {
|
||||
case 1:
|
||||
l.logger.Info().Msg("pre-upgrade command does not exist. continuing the upgrade.")
|
||||
l.logger.Info("pre-upgrade command does not exist. continuing the upgrade.")
|
||||
return nil
|
||||
case 30:
|
||||
return fmt.Errorf("pre-upgrade command failed : %w", err)
|
||||
case 31:
|
||||
l.logger.Error().Err(err).Int("attempt", counter).Msg("pre-upgrade command failed. retrying")
|
||||
l.logger.Error("pre-upgrade command failed. retrying", "error", err, "attempt", counter)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
l.logger.Info().Msg("pre-upgrade successful. continuing the upgrade.")
|
||||
l.logger.Info("pre-upgrade successful. continuing the upgrade.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -273,7 +274,7 @@ func (l *Launcher) executePreUpgradeCmd() error {
|
||||
return err
|
||||
}
|
||||
|
||||
l.logger.Info().Bytes("result", result).Msg("pre-upgrade result")
|
||||
l.logger.Info("pre-upgrade result", "result", result)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -93,7 +93,6 @@ func (s *processTestSuite) TestLaunchProcessWithRestartDelay() {
|
||||
upgradeFile := cfg.UpgradeInfoFilePath()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
doUpgrade, err := launcher.Run([]string{"foo", "bar", "1234", upgradeFile}, stdout, stderr)
|
||||
require.NoError(err)
|
||||
require.True(doUpgrade)
|
||||
@ -115,8 +114,7 @@ func (s *processTestSuite) TestLaunchProcessWithDownloads() {
|
||||
require := s.Require()
|
||||
home := copyTestData(s.T(), "download")
|
||||
cfg := &cosmovisor.Config{Home: home, Name: "autod", AllowDownloadBinaries: true, PollInterval: 100, UnsafeSkipBackup: true}
|
||||
buf := newBuffer() // inspect output using buf.String()
|
||||
logger := log.NewLogger(buf).With(log.ModuleKey, "cosmovisor")
|
||||
logger := log.NewTestLogger(s.T()).With(log.ModuleKey, "cosmovisor")
|
||||
upgradeFilename := cfg.UpgradeInfoFilePath()
|
||||
|
||||
// should run the genesis binary and produce expected output
|
||||
@ -130,7 +128,6 @@ func (s *processTestSuite) TestLaunchProcessWithDownloads() {
|
||||
stdout, stderr := newBuffer(), newBuffer()
|
||||
args := []string{"some", "args", upgradeFilename}
|
||||
doUpgrade, err := launcher.Run(args, stdout, stderr)
|
||||
|
||||
require.NoError(err)
|
||||
require.True(doUpgrade)
|
||||
require.Equal("", stderr.String())
|
||||
@ -180,14 +177,14 @@ func (s *processTestSuite) TestLaunchProcessWithDownloadsAndMissingPreupgrade()
|
||||
require := s.Require()
|
||||
home := copyTestData(s.T(), "download")
|
||||
cfg := &cosmovisor.Config{
|
||||
Home: home, Name: "autod",
|
||||
Home: home,
|
||||
Name: "autod",
|
||||
AllowDownloadBinaries: true,
|
||||
PollInterval: 100,
|
||||
UnsafeSkipBackup: true,
|
||||
CustomPreupgrade: "missing.sh",
|
||||
}
|
||||
buf := newBuffer() // inspect output using buf.String()
|
||||
logger := log.NewLogger(buf).With(log.ModuleKey, "cosmovisor")
|
||||
logger := log.NewTestLogger(s.T()).With(log.ModuleKey, "cosmovisor")
|
||||
upgradeFilename := cfg.UpgradeInfoFilePath()
|
||||
|
||||
// should run the genesis binary and produce expected output
|
||||
|
||||
@ -5,57 +5,59 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
upgradetypes "cosmossdk.io/x/upgrade/types"
|
||||
)
|
||||
|
||||
type fileWatcher struct {
|
||||
logger log.Logger
|
||||
|
||||
// full path to a watched file
|
||||
filename string
|
||||
filename string // full path to a watched file
|
||||
interval time.Duration
|
||||
|
||||
currentBin string
|
||||
currentInfo upgradetypes.Plan
|
||||
lastModTime time.Time
|
||||
cancel chan bool
|
||||
ticker *time.Ticker
|
||||
needsUpdate bool
|
||||
|
||||
needsUpdate bool
|
||||
initialized bool
|
||||
}
|
||||
|
||||
func newUpgradeFileWatcher(logger log.Logger, filename string, interval time.Duration) (*fileWatcher, error) {
|
||||
func newUpgradeFileWatcher(cfg *Config, logger log.Logger) (*fileWatcher, error) {
|
||||
filename := cfg.UpgradeInfoFilePath()
|
||||
if filename == "" {
|
||||
return nil, errors.New("filename undefined")
|
||||
}
|
||||
|
||||
filenameAbs, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return nil,
|
||||
fmt.Errorf("invalid path; %s must be a valid file path: %w", filename, err)
|
||||
return nil, fmt.Errorf("invalid path: %s must be a valid file path: %w", filename, err)
|
||||
}
|
||||
|
||||
dirname := filepath.Dir(filename)
|
||||
info, err := os.Stat(dirname)
|
||||
if err != nil || !info.IsDir() {
|
||||
return nil, fmt.Errorf("invalid path; %s must be an existing directory: %w", dirname, err)
|
||||
if info, err := os.Stat(dirname); err != nil || !info.IsDir() {
|
||||
return nil, fmt.Errorf("invalid path: %s must be an existing directory: %w", dirname, err)
|
||||
}
|
||||
|
||||
bin, err := cfg.CurrentBin()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating symlink to genesis: %w", err)
|
||||
}
|
||||
|
||||
return &fileWatcher{
|
||||
logger: logger,
|
||||
currentBin: bin,
|
||||
filename: filenameAbs,
|
||||
interval: interval,
|
||||
interval: cfg.PollInterval,
|
||||
currentInfo: upgradetypes.Plan{},
|
||||
lastModTime: time.Time{},
|
||||
cancel: make(chan bool),
|
||||
ticker: time.NewTicker(interval),
|
||||
ticker: time.NewTicker(cfg.PollInterval),
|
||||
needsUpdate: false,
|
||||
initialized: false,
|
||||
}, nil
|
||||
@ -65,9 +67,9 @@ func (fw *fileWatcher) Stop() {
|
||||
close(fw.cancel)
|
||||
}
|
||||
|
||||
// pools the filesystem to check for new upgrade currentInfo. currentName is the name
|
||||
// of currently running upgrade. The check is rejected if it finds an upgrade with the same
|
||||
// name.
|
||||
// MonitorUpdate pools the filesystem to check for new upgrade currentInfo.
|
||||
// currentName is the name of currently running upgrade. The check is rejected if it finds
|
||||
// an upgrade with the same name.
|
||||
func (fw *fileWatcher) MonitorUpdate(currentUpgrade upgradetypes.Plan) <-chan struct{} {
|
||||
fw.ticker.Reset(fw.interval)
|
||||
done := make(chan struct{})
|
||||
@ -112,8 +114,12 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
|
||||
|
||||
info, err := parseUpgradeInfoFile(fw.filename)
|
||||
if err != nil {
|
||||
zl := fw.logger.Impl().(*zerolog.Logger)
|
||||
zl.Fatal().Err(err).Msg("failed to parse upgrade info file")
|
||||
panic(fmt.Errorf("failed to parse upgrade info file: %w", err))
|
||||
}
|
||||
|
||||
// file exist but too early in height
|
||||
currentHeight, _ := fw.checkHeight()
|
||||
if currentHeight != 0 && currentHeight < info.Height {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -142,27 +148,60 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) {
|
||||
var ui upgradetypes.Plan
|
||||
// checkHeight checks if the current block height
|
||||
func (fw *fileWatcher) checkHeight() (int64, error) {
|
||||
// TODO(@julienrbrt) use `if !testing.Testing()` from Go 1.22
|
||||
// The tests from `process_test.go`, which run only on linux, are failing when using `autod` that is a bash script.
|
||||
// In production, the binary will always be an application with a status command, but in tests it isn't not.
|
||||
if strings.HasSuffix(os.Args[0], ".test") {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
result, err := exec.Command(fw.currentBin, "status").Output() //nolint:gosec // we want to execute the status command
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
type response struct {
|
||||
SyncInfo struct {
|
||||
LatestBlockHeight string `json:"latest_block_height"`
|
||||
} `json:"SyncInfo"`
|
||||
}
|
||||
|
||||
var resp response
|
||||
if err := json.Unmarshal(result, &resp); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if resp.SyncInfo.LatestBlockHeight == "" {
|
||||
return 0, errors.New("latest block height is empty")
|
||||
}
|
||||
|
||||
return strconv.ParseInt(resp.SyncInfo.LatestBlockHeight, 10, 64)
|
||||
}
|
||||
|
||||
func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) {
|
||||
f, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return upgradetypes.Plan{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
d := json.NewDecoder(f)
|
||||
if err := d.Decode(&ui); err != nil {
|
||||
if len(f) == 0 {
|
||||
return upgradetypes.Plan{}, errors.New("empty upgrade-info.json")
|
||||
}
|
||||
|
||||
var upgradePlan upgradetypes.Plan
|
||||
if err := json.Unmarshal(f, &upgradePlan); err != nil {
|
||||
return upgradetypes.Plan{}, err
|
||||
}
|
||||
|
||||
// required values must be set
|
||||
if ui.Height <= 0 || ui.Name == "" {
|
||||
return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content; name and height must be not empty; got: %v", ui)
|
||||
if err := upgradePlan.ValidateBasic(); err != nil {
|
||||
return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content: %w, got: %v", err, upgradePlan)
|
||||
}
|
||||
|
||||
// normalize name to prevent operator error in upgrade name case sensitivity errors.
|
||||
ui.Name = strings.ToLower(ui.Name)
|
||||
upgradePlan.Name = strings.ToLower(upgradePlan.Name)
|
||||
|
||||
return ui, err
|
||||
return upgradePlan, err
|
||||
}
|
||||
|
||||
@ -31,10 +31,6 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
)
|
||||
|
||||
// Deprecated: UpgradeInfoFileName file to store upgrade information
|
||||
// use x/upgrade/types.UpgradeInfoFilename instead.
|
||||
const UpgradeInfoFileName string = "upgrade-info.json"
|
||||
|
||||
type Keeper struct {
|
||||
homePath string // root directory of app config
|
||||
skipUpgradeHeights map[int64]bool // map of heights to skip for an upgrade
|
||||
|
||||
Loading…
Reference in New Issue
Block a user