Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat (tests): enable multiple upgrades for automated upgrade tests #1283

Merged
merged 32 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3c5569d
enable multiple upgrades for automated upgrade tests
MalteHerrmann Jan 25, 2023
3502dfb
refactor get logs from docker container
MalteHerrmann Jan 25, 2023
94abb47
check error string when trying to kill node
MalteHerrmann Jan 25, 2023
794d252
remove unnecessary deposit step
MalteHerrmann Jan 25, 2023
860ebdd
escape linter regarding preallocation of upgrades
MalteHerrmann Jan 25, 2023
263c27a
export byversion
MalteHerrmann Jan 25, 2023
602a4a7
enable backwards compatibility with submit-proposal
MalteHerrmann Jan 25, 2023
3659ce0
add logs to waitforheight error
MalteHerrmann Jan 25, 2023
bdf19cb
fix submit-legacy-proposal
MalteHerrmann Jan 25, 2023
d3562d0
Merge branch 'main' into malte/multiple-upgrades-in-tests
MalteHerrmann Jan 26, 2023
ae8188a
adjust logger in e2e_suite_test.go
MalteHerrmann Jan 26, 2023
80613de
refactor checkLegacyProposal
MalteHerrmann Jan 26, 2023
a613ef9
address review comments
MalteHerrmann Jan 26, 2023
ed7f2e9
rename byversion to EvmosVersion
MalteHerrmann Jan 26, 2023
31a80fc
add tests and rename to plural evmosversions
MalteHerrmann Jan 26, 2023
7f45846
rename upgradeConfig to versionConfig + housekeeping
MalteHerrmann Jan 26, 2023
083d6c5
add node version sanity check after upgrade
MalteHerrmann Jan 26, 2023
46c4836
update changelog and readme
MalteHerrmann Jan 26, 2023
239ec6b
address markdown linter comments
MalteHerrmann Jan 26, 2023
278cd5f
address more markdown linter comments
MalteHerrmann Jan 26, 2023
ba52c64
Update tests/e2e/README.md
MalteHerrmann Jan 26, 2023
d03a83e
Merge branch 'main' into malte/multiple-upgrades-in-tests
MalteHerrmann Jan 26, 2023
aa65ce5
execute e2e unit tests during make test-unit and not make test-upgrade
MalteHerrmann Jan 26, 2023
52ba148
add more info to e2e readme
MalteHerrmann Jan 26, 2023
8779ced
fix markdownlink-check
MalteHerrmann Jan 26, 2023
69c35e4
remove unused commands from init script for upgrade tests
MalteHerrmann Jan 26, 2023
571a6fe
use custom pruning settings
MalteHerrmann Jan 26, 2023
4adb8e0
Merge branch 'main' into malte/multiple-upgrades-in-tests
fedekunze Jan 26, 2023
03dae00
use variable to decide whether custom pruning settings are used
MalteHerrmann Jan 26, 2023
72da00f
increase test coverage for EvmosVersions
MalteHerrmann Jan 27, 2023
1793ee9
refactor and add tests for function to retrieve upgrades list
MalteHerrmann Jan 27, 2023
80f3b50
Merge branch 'main' into malte/multiple-upgrades-in-tests
MalteHerrmann Jan 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified Makefile
100755 → 100644
Empty file.
132 changes: 77 additions & 55 deletions tests/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@ const (
defaultChainID = "evmos_9000-1"
defaultManagerNetwork = "evmos-local"
tharsisRepo = "tharsishq/evmos"
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved
firstUpgradeHeight = 25

// blocksAfterUpgrade defines how many blocks must be produced after an upgrade is
// considered successful
blocksAfterUpgrade = 5
// upgradeHeightDelta defines the number of blocks after the proposal and the scheduled upgrade
upgradeHeightDelta = 10

relatedBuildPath = "../../build/"
)

type upgradeParams struct {
InitialVersion string
TargetVersion string
SoftwareUpgradeVersion string
MountPath string
MountPath string
Upgrades []upgradeConfig

ChainID string
TargetRepo string
Expand Down Expand Up @@ -61,105 +64,118 @@ func (s *IntegrationTestSuite) SetupSuite() {
}
}

func (s *IntegrationTestSuite) runInitialNode() {
// runInitialNode builds a docker image capable of running an Evmos node with the given version.
// After a successful build, it runs the container and checks if the node can produce blocks.
func (s *IntegrationTestSuite) runInitialNode(version string) {
err := s.upgradeManager.BuildImage(
localRepository,
s.upgradeParams.InitialVersion,
version,
"./upgrade/Dockerfile.init",
".",
map[string]string{"INITIAL_VERSION": s.upgradeParams.InitialVersion},
map[string]string{"INITIAL_VERSION": version},
)
s.Require().NoError(err, "can't build initial container")
s.Require().NoError(err, "can't build container with Evmos version: %s", version)

node := upgrade.NewNode(localRepository, s.upgradeParams.InitialVersion)
node := upgrade.NewNode(localRepository, version)
node.SetEnvVars([]string{fmt.Sprintf("CHAIN_ID=%s", s.upgradeParams.ChainID)})

err = s.upgradeManager.RunNode(node)
s.Require().NoError(err, "can't run initial node")
s.Require().NoError(err, "can't run node with Evmos version: %s", version)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// wait until node starts and produce some blocks
err = s.upgradeManager.WaitForHeight(ctx, 5)
err = s.upgradeManager.WaitForHeight(ctx, s.upgradeManager.HeightBeforeStop+5)
s.Require().NoError(err)

s.T().Logf("successfully started initial node version: [%s]", s.upgradeParams.InitialVersion)
s.T().Logf("successfully started node with Evmos version: [%s]", version)
}

func (s *IntegrationTestSuite) proposeUpgrade() {
// proposeUpgrade submits an upgrade proposal to the chain that schedules an upgrade to
// the given target version.
func (s *IntegrationTestSuite) proposeUpgrade(name, target string) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// calculate upgrade height for the proposal
nodeHeight, err := s.upgradeManager.GetNodeHeight(ctx)
s.Require().NoError(err, "can't get block height from running node")
s.upgradeManager.UpgradeHeight = uint(nodeHeight + upgradeHeightDelta)

// if Evmos is lower than v10.x.x no need to use the legacy proposal
currentVersion, err := s.upgradeManager.GetNodeVersion(ctx)
s.Require().NoError(err, "can't get evmosd version from running node")
currentVersion = strings.TrimSpace(currentVersion)
if !strings.HasPrefix(currentVersion, "v") {
currentVersion = "v" + currentVersion
}
s.T().Logf("current version: '%s'", currentVersion)
cmp := upgrade.ByVersion([]string{currentVersion, "v10.0.0"})
legacyProposal := !cmp.Less(0, 1)
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved
s.T().Logf("legacy proposal: %v", legacyProposal)
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved

// create the proposal
exec, err := s.upgradeManager.CreateSubmitProposalExec(
s.upgradeParams.SoftwareUpgradeVersion,
name,
s.upgradeParams.ChainID,
firstUpgradeHeight,
s.upgradeManager.UpgradeHeight,
legacyProposal,
)
s.Require().NoError(err, "can't create submit proposal exec")
outBuf, errBuf, err := s.upgradeManager.RunExec(ctx, exec)
s.Require().NoErrorf(
err,
"failed to submit upgrade proposal; stdout: %s, stderr: %s", outBuf, errBuf,
"can't create the proposal to upgrade Evmos to %s at height %d with name %s",
target, s.upgradeManager.UpgradeHeight, name,
)

s.Require().Truef(
strings.Contains(outBuf.String(), "code: 0"),
"tx returned non code 0: %s %s", outBuf, errBuf,
)

s.T().Logf(
"successfully submitted upgrade proposal: upgrade height: [%d] upgrade version: [%s]",
firstUpgradeHeight,
s.upgradeParams.SoftwareUpgradeVersion,
)
}

func (s *IntegrationTestSuite) depositToProposal() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
exec, err := s.upgradeManager.CreateDepositProposalExec(s.upgradeParams.ChainID)
s.Require().NoError(err, "can't create deposit to proposal tx exec")
outBuf, errBuf, err := s.upgradeManager.RunExec(ctx, exec)
s.Require().NoErrorf(
err,
"failed to submit deposit to proposal tx; stdout: %s, stderr: %s", outBuf, errBuf,
"failed to submit proposal to upgrade Evmos to %s at height %d\nstdout: %s,\nstderr: %s",
target, s.upgradeManager.UpgradeHeight, outBuf.String(), errBuf.String(),
)

s.Require().Truef(
strings.Contains(outBuf.String(), "code: 0"),
"tx returned non code 0: %s %s", outBuf, errBuf,
"tx returned non code 0:\nstdout: %s\nstderr: %s", outBuf.String(), errBuf.String(),
)

s.T().Logf("successfully deposited to proposal")
s.T().Logf(
"successfully submitted upgrade proposal: height: %d, name: %s",
s.upgradeManager.UpgradeHeight,
target,
)
}

func (s *IntegrationTestSuite) voteForProposal() {
// voteForProposal votes for the upgrade proposal with the given id.
func (s *IntegrationTestSuite) voteForProposal(id int) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
exec, err := s.upgradeManager.CreateVoteProposalExec(s.upgradeParams.ChainID)
exec, err := s.upgradeManager.CreateVoteProposalExec(s.upgradeParams.ChainID, id)
s.Require().NoError(err, "can't create vote for proposal exec")
outBuf, errBuf, err := s.upgradeManager.RunExec(ctx, exec)
s.Require().NoErrorf(
err,
"failed to vote for proposal tx; stdout: %s, stderr: %s", outBuf, errBuf,
"failed to vote for proposal tx;\nstdout: %s,\nstderr: %s", outBuf.String(), errBuf.String(),
)

s.Require().Truef(
strings.Contains(outBuf.String(), "code: 0"),
"tx returned non code 0: %s %s", outBuf, errBuf,
"tx returned non code 0:\nstdout: %s\nstderr: %s", outBuf.String(), errBuf.String(),
)

s.T().Logf("successfully voted for upgrade proposal")
}

func (s *IntegrationTestSuite) upgrade() {
// upgrade upgrades the node to the given version using the given repo. The repository
// can either be a local path or a remote repository.
func (s *IntegrationTestSuite) upgrade(targetRepo, targetVersion string) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

s.T().Log("wait for node to reach upgrade height...")
// wait for proposed upgrade height
err := s.upgradeManager.WaitForHeight(ctx, firstUpgradeHeight)
err := s.upgradeManager.WaitForHeight(ctx, int(s.upgradeManager.UpgradeHeight))
s.Require().NoError(err, "can't reach upgrade height")
buildDir := strings.Split(s.upgradeParams.MountPath, ":")[0]

Expand All @@ -168,37 +184,43 @@ func (s *IntegrationTestSuite) upgrade() {
err = s.upgradeManager.ExportState(buildDir)
s.Require().NoError(err, "can't export node container state to local")

s.T().Log("killing initial node...")
s.T().Log("killing node before upgrade...")
err = s.upgradeManager.KillCurrentNode()
s.Require().NoError(err, "can't kill current node")

s.T().Logf(
"starting upgraded node: version: [%s] mount point: [%s]",
s.upgradeParams.TargetVersion,
"starting upgraded node: repo: [%s] version: [%s] mount point: [%s]",
targetRepo,
targetVersion,
s.upgradeParams.MountPath,
)

node := upgrade.NewNode(s.upgradeParams.TargetRepo, s.upgradeParams.TargetVersion)
node := upgrade.NewNode(targetRepo, targetVersion)
node.Mount(s.upgradeParams.MountPath)
node.SetCmd([]string{"evmosd", "start", fmt.Sprintf("--chain-id=%s", s.upgradeParams.ChainID)})
err = s.upgradeManager.RunNode(node)
s.Require().NoError(err, "can't mount and run upgraded node container")

s.T().Log("node started! waiting for node to produce 10 blocks")
s.T().Logf("node started! waiting for node to produce %d blocks", blocksAfterUpgrade)
// make sure node produce blocks after upgrade
err = s.upgradeManager.WaitForHeight(ctx, firstUpgradeHeight+10)
s.Require().NoError(err, "node not produce blocks")
err = s.upgradeManager.WaitForHeight(ctx, int(s.upgradeManager.UpgradeHeight)+blocksAfterUpgrade)
s.Require().NoError(err, "node does not produce blocks after upgrade")
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved
}

// TearDownSuite kills the running container, removes the network and mount path
func (s *IntegrationTestSuite) TearDownSuite() {
if s.upgradeParams.SkipCleanup {
s.T().Logf("skipping cleanup... container %s will be left running", s.upgradeManager.ContainerID())
return
}
s.T().Log("tearing down e2e integration test suite...")

s.Require().NoError(s.upgradeManager.KillCurrentNode())
err := s.upgradeManager.KillCurrentNode()
if err != nil && !strings.Contains(err.Error(), "is not running") {
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved
s.Require().NoError(err, "can't kill current node")
}

s.Require().NoError(s.upgradeManager.RemoveNetwork())
s.Require().NoError(s.upgradeManager.RemoveNetwork(), "can't remove network")

s.Require().NoError(os.RemoveAll(strings.Split(s.upgradeParams.MountPath, ":")[0]))
s.Require().NoError(os.RemoveAll(strings.Split(s.upgradeParams.MountPath, ":")[0]), "can't remove mount path")
}
21 changes: 16 additions & 5 deletions tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package e2e

// TestUpgrade tests if an Evmos node can be upgraded from one version to another.
// It iterates through the list of scheduled upgrades, that are defined using the input
// arguments to the make command. The function then submits a proposal to upgrade the chain,
// and finally upgrades the chain.
// If the chain can be restarted after the upgrade(s), the test passes.
func (s *IntegrationTestSuite) TestUpgrade() {
s.runInitialNode()
s.proposeUpgrade()
s.depositToProposal()
s.voteForProposal()
s.upgrade()
for idx, upgrade := range s.upgradeParams.Upgrades {
if idx == 0 {
// start initial node
s.runInitialNode(upgrade.version)
continue
}
s.T().Logf("(upgrade %d): UPGRADING TO %s WITH PROPOSAL NAME %s", idx, upgrade.version, upgrade.name)
s.proposeUpgrade(upgrade.name, upgrade.version)
s.voteForProposal(idx)
s.upgrade(upgrade.repo, upgrade.version)
}
s.T().Logf("SUCCESS")
}
68 changes: 55 additions & 13 deletions tests/e2e/e2e_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,77 @@ import (
"strings"
)

// versionSeparator is used to separate versions in the INITIAL_VERSION and TARGET_VERSION
// environment vars
const versionSeparator = "/"

// upgradeConfig defines a struct that contains the version and the source repository for an upgrade
type upgradeConfig struct {
name string
version string
repo string
}

// loadUpgradeParams loads the parameters for the upgrade test suite from the environment
// variables
func (s *IntegrationTestSuite) loadUpgradeParams() {
var err error
// name defines the upgrade name to use in the proposal
var name string
// targetRepo is assigned to the remote repository by default and is changed to local if no
// target version is given
targetRepo := tharsisRepo
// upgradesList contains the available upgrades in the app/upgrades folder
var upgradesList []string
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved
// upgrades contains the slice of all upgrades that shall be executed
var upgrades []upgradeConfig //nolint:prealloc
// version defines the version to run the Evmos node with
var version string
// versions contains the slice of all versions that are run during the upgrade tests
var versions []string

initialV := os.Getenv("INITIAL_VERSION")
if initialV == "" {
upgradesList, err := s.upgradeManager.RetrieveUpgradesList()
upgradesList, err = s.upgradeManager.RetrieveUpgradesList()
s.Require().NoError(err)
// set the pre-last upgrade is upgrade list
s.upgradeParams.InitialVersion = upgradesList[len(upgradesList)-2]
// set the second-to-last upgrade as initial version
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved
versions = []string{upgradesList[len(upgradesList)-2]}
} else {
s.upgradeParams.InitialVersion = initialV
versions = strings.Split(initialV, versionSeparator)
}

// for all initial versions define the docker hub repo as the source
for _, version := range versions {
upgrades = append(upgrades, upgradeConfig{
version,
version,
targetRepo,
})
}

// Target version loading, if not specified manager gets the last one from app/upgrades folder
// and sets target repository to local, otherwise tharsishq repo will be used
targetV := os.Getenv("TARGET_VERSION")
if targetV == "" {
upgradesList, err := s.upgradeManager.RetrieveUpgradesList()
s.Require().NoError(err)
// set the last upgrade is upgrade list
s.upgradeParams.SoftwareUpgradeVersion = upgradesList[len(upgradesList)-1]
s.upgradeParams.TargetVersion = localVersionTag
s.upgradeParams.TargetRepo = tharsisRepo
if upgradesList == nil {
upgradesList, err = s.upgradeManager.RetrieveUpgradesList()
s.Require().NoError(err)
}
name = upgradesList[len(upgradesList)-1]
version = localVersionTag
} else {
s.upgradeParams.TargetVersion = targetV
s.upgradeParams.SoftwareUpgradeVersion = targetV
s.upgradeParams.TargetRepo = tharsisRepo
name = targetV
version = targetV
}

// Add the target version to the upgrades slice
upgrades = append(upgrades, upgradeConfig{
name,
version,
targetRepo,
})
s.upgradeParams.Upgrades = upgrades

// If chain ID is not specified, 'evmos_9000-1' will be used in upgrade-init.sh
chID := os.Getenv("CHAIN_ID")
if chID == "" {
Expand Down
Loading