Skip to content

Commit

Permalink
feat(cosmovisor): Cosmovisor upgrade plan without automatic lower case (
Browse files Browse the repository at this point in the history
  • Loading branch information
chillyvee authored Jul 12, 2023
1 parent 1b0fcdc commit c5df6a3
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 80 deletions.
1 change: 1 addition & 0 deletions tools/cosmovisor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Improvements

* [#16919](https://github.com/cosmos/cosmos-sdk/pull/16919) Add COSMOVISOR_DISABLE_RECASE to cosmovisor to disable automatic case change for plan name
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to use `x/upgrade` validation logic.
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to depend only on the `x/upgrade` module.
* [#15362](https://github.com/cosmos/cosmos-sdk/pull/15362) Allow disabling Cosmovisor logs
Expand Down
1 change: 1 addition & 0 deletions tools/cosmovisor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Use of `cosmovisor` without one of the action arguments is deprecated. For backw
* `COSMOVISOR_COLOR_LOGS` (defaults to `true`). If set to true, this will colorise Cosmovisor logs (but not the underlying process).
* `COSMOVISOR_TIMEFORMAT_LOGS` (defaults to `kitchen`). If set to a value (`layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen`), this will add timestamp prefix to Cosmovisor logs (but not the underlying process).
* `COSMOVISOR_CUSTOM_PREUPGRADE` (defaults to ``). If set, this will run $DAEMON_HOME/cosmovisor/$COSMOVISOR_CUSTOM_PREUPGRADE prior to upgrade with the arguments [ upgrade.Name, upgrade.Height ]. Executes a custom script (separate and prior to the chain daemon pre-upgrade command)
* `COSMOVISOR_DISABLE_RECASE` (defaults to `false`). If set to true, the upgrade directory will expected to match the upgrade plan name without any case changes

### Folder Layout

Expand Down
5 changes: 5 additions & 0 deletions tools/cosmovisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
EnvColorLogs = "COSMOVISOR_COLOR_LOGS"
EnvTimeFormatLogs = "COSMOVISOR_TIMEFORMAT_LOGS"
EnvCustomPreupgrade = "COSMOVISOR_CUSTOM_PREUPGRADE"
EnvDisableRecase = "COSMOVISOR_DISABLE_RECASE"
)

const (
Expand All @@ -58,6 +59,7 @@ type Config struct {
ColorLogs bool
TimeFormatLogs string
CustomPreupgrade string
DisableRecase bool

// currently running upgrade
currentUpgrade upgradetypes.Plan
Expand Down Expand Up @@ -178,6 +180,9 @@ func GetConfigFromEnv() (*Config, error) {
if cfg.TimeFormatLogs, err = TimeFormatOptionFromEnv(EnvTimeFormatLogs, time.Kitchen); err != nil {
errs = append(errs, err)
}
if cfg.DisableRecase, err = BooleanOption(EnvDisableRecase, false); err != nil {
errs = append(errs, err)
}

interval := os.Getenv(EnvInterval)
if interval != "" {
Expand Down
141 changes: 80 additions & 61 deletions tools/cosmovisor/args_test.go

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions tools/cosmovisor/cmd/cosmovisor/add_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -37,9 +38,9 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {

logger := cfg.Logger(os.Stdout)

upgradeName := strings.ToLower(args[0])
if len(upgradeName) == 0 {
return fmt.Errorf("upgrade name cannot be empty")
upgradeName := args[0]
if !cfg.DisableRecase {
upgradeName = strings.ToLower(args[0])
}

executablePath := args[1]
Expand Down Expand Up @@ -93,7 +94,7 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
return err
}

logger.Info(fmt.Sprintf("%s created, %s upgrade binary will switch at height %d", upgradetypes.UpgradeInfoFilename, upgradeName, upgradeHeight))
logger.Info(fmt.Sprintf("%s created, %s upgrade binary will switch at height %d", filepath.Join(cfg.UpgradeInfoFilePath(), upgradetypes.UpgradeInfoFilename), upgradeName, upgradeHeight))
}

return nil
Expand Down
45 changes: 45 additions & 0 deletions tools/cosmovisor/process_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,51 @@ func (s *processTestSuite) TestLaunchProcess() {
require.Equal(cfg.UpgradeBin("chain2"), currentBin)
}

// TestPlanDisableRecase will test upgrades without lower case plan names
func (s *processTestSuite) TestPlanDisableRecase() {
// binaries from testdata/validate directory
require := s.Require()
home := copyTestData(s.T(), "norecase")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 20, UnsafeSkipBackup: true, DisableRecase: true}
logger := log.NewTestLogger(s.T()).With(log.ModuleKey, "cosmosvisor")

// 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()

args := []string{"foo", "bar", "1234", upgradeFile}
doUpgrade, err := launcher.Run(args, stdout, stderr)
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())

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

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

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

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

func (s *processTestSuite) TestLaunchProcessWithRestartDelay() {
// binaries from testdata/validate directory
require := s.Require()
Expand Down
32 changes: 18 additions & 14 deletions tools/cosmovisor/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ type fileWatcher struct {
cancel chan bool
ticker *time.Ticker

needsUpdate bool
initialized bool
needsUpdate bool
initialized bool
disableRecase bool
}

func newUpgradeFileWatcher(cfg *Config, logger log.Logger) (*fileWatcher, error) {
Expand All @@ -51,15 +52,16 @@ func newUpgradeFileWatcher(cfg *Config, logger log.Logger) (*fileWatcher, error)
}

return &fileWatcher{
currentBin: bin,
filename: filenameAbs,
interval: cfg.PollInterval,
currentInfo: upgradetypes.Plan{},
lastModTime: time.Time{},
cancel: make(chan bool),
ticker: time.NewTicker(cfg.PollInterval),
needsUpdate: false,
initialized: false,
currentBin: bin,
filename: filenameAbs,
interval: cfg.PollInterval,
currentInfo: upgradetypes.Plan{},
lastModTime: time.Time{},
cancel: make(chan bool),
ticker: time.NewTicker(cfg.PollInterval),
needsUpdate: false,
initialized: false,
disableRecase: cfg.DisableRecase,
}, nil
}

Expand Down Expand Up @@ -112,7 +114,7 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
return false
}

info, err := parseUpgradeInfoFile(fw.filename)
info, err := parseUpgradeInfoFile(fw.filename, fw.disableRecase)
if err != nil {
panic(fmt.Errorf("failed to parse upgrade info file: %w", err))
}
Expand Down Expand Up @@ -180,7 +182,7 @@ func (fw *fileWatcher) checkHeight() (int64, error) {
return strconv.ParseInt(resp.SyncInfo.LatestBlockHeight, 10, 64)
}

func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) {
func parseUpgradeInfoFile(filename string, disableRecase bool) (upgradetypes.Plan, error) {
f, err := os.ReadFile(filename)
if err != nil {
return upgradetypes.Plan{}, err
Expand All @@ -201,7 +203,9 @@ func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) {
}

// normalize name to prevent operator error in upgrade name case sensitivity errors.
upgradePlan.Name = strings.ToLower(upgradePlan.Name)
if !disableRecase {
upgradePlan.Name = strings.ToLower(upgradePlan.Name)
}

return upgradePlan, err
}
18 changes: 17 additions & 1 deletion tools/cosmovisor/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,66 @@ func TestParseUpgradeInfoFile(t *testing.T) {
cases := []struct {
filename string
expectUpgrade upgradetypes.Plan
disableRecase bool
expectErr bool
}{
{
filename: "f1-good.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{Name: "upgrade1", Info: "some info", Height: 123},
expectErr: false,
},
{
filename: "f2-normalized-name.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{Name: "upgrade2", Info: "some info", Height: 125},
expectErr: false,
},
{
filename: "f2-normalized-name.json",
disableRecase: true,
expectUpgrade: upgradetypes.Plan{Name: "Upgrade2", Info: "some info", Height: 125},
expectErr: false,
},
{
filename: "f2-bad-type.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
},
{
filename: "f2-bad-type-2.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
},
{
filename: "f3-empty.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
},
{
filename: "f4-empty-obj.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
},
{
filename: "f5-partial-obj-1.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
},
{
filename: "f5-partial-obj-2.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
},
{
filename: "unknown.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
},
Expand All @@ -66,7 +82,7 @@ func TestParseUpgradeInfoFile(t *testing.T) {
tc := cases[i]
t.Run(tc.filename, func(t *testing.T) {
require := require.New(t)
ui, err := parseUpgradeInfoFile(filepath.Join(".", "testdata", "upgrade-files", tc.filename))
ui, err := parseUpgradeInfoFile(filepath.Join(".", "testdata", "upgrade-files", tc.filename), tc.disableRecase)
if tc.expectErr {
require.Error(err)
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh

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

echo Chain 2 is live!
echo Args: $@
sleep 1
echo Finished successfully
Empty file.

0 comments on commit c5df6a3

Please sign in to comment.