From a7763f09bb07b7c0548eeb5381dc1b05edea0faf Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Sat, 28 May 2022 13:31:07 +0200 Subject: [PATCH] create versioned e2e docker image --- .github/workflows/docker.yml | 2 +- .github/workflows/e2e-docker.yml | 55 +++++++++++++++++++ test/e2e/Makefile | 8 ++- test/e2e/networks/ci.toml | 2 +- test/e2e/pkg/manifest.go | 3 + test/e2e/pkg/testnet.go | 22 ++++++++ test/e2e/runner/main.go | 8 +++ test/e2e/runner/setup.go | 2 +- test/e2e/runner/upgrade.go | 94 ++++++++++++++++++++++++++++++++ 9 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/e2e-docker.yml create mode 100644 test/e2e/runner/upgrade.go diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0a006f9b9..6cf8d558e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,6 @@ name: Docker # Build & Push rebuilds the tendermint docker image on every push to master and creation of tags -# and pushes the image to https://hub.docker.com/r/interchainio/simapp/tags +# and pushes the image to https://hub.docker.com/r/tendermint/tendermint/tags on: push: branches: diff --git a/.github/workflows/e2e-docker.yml b/.github/workflows/e2e-docker.yml new file mode 100644 index 000000000..4f00289a1 --- /dev/null +++ b/.github/workflows/e2e-docker.yml @@ -0,0 +1,55 @@ +name: E2E Docker +# builds and pushes tendermint e2e-node docker image on every creation of a tag +# and pushes the image to https://hub.docker.com/r/tendermint/e2e-node/tags +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Prepare + id: prep + run: | + DOCKER_IMAGE=tendermint/e2e-node + VERSION=noop + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + elif [[ $GITHUB_REF == refs/heads/* ]]; then + VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g') + if [ "${{ github.event.repository.default_branch }}" = "$VERSION" ]; then + VERSION=latest + fi + fi + TAGS="${DOCKER_IMAGE}:${VERSION}" + if [[ $VERSION =~ ^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + TAGS="$TAGS,${DOCKER_IMAGE}:${VERSION}" + fi + echo ::set-output name=tags::${TAGS} + + - name: Set up QEMU + uses: docker/setup-qemu-action@master + with: + platforms: all + + - name: Set up Docker Build + uses: docker/setup-buildx-action@v2.0.0 + + - name: Login to DockerHub + if: ${{ github.event_name != 'pull_request' }} + uses: docker/login-action@v2.0.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Publish to Docker Hub + uses: docker/build-push-action@v3.0.0 + with: + context: . + file: ./test/e2e/docker/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.prep.outputs.tags }} diff --git a/test/e2e/Makefile b/test/e2e/Makefile index 23cf4d039..00cd4f512 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -1,7 +1,13 @@ all: docker generator runner tests docker: - docker build --tag tendermint/e2e-node -f docker/Dockerfile ../.. +ifneq (,$(VERSION)) + git worktree add e2e-node $(VERSION) + docker build --tag tendermint/e2e-node:$(VERSION) -f docker/Dockerfile ../.. + git worktree remove e2e-node +else + docker build --tag tendermint/e2e-node:$(shell git symbolic-ref -q --short HEAD) -f docker/Dockerfile ../.. +endif node: go build -o build/node -tags badgerdb,boltdb,cleveldb,rocksdb ./node diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index e2f7376a2..8551cf609 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -1,6 +1,6 @@ # This testnet is run by CI, and attempts to cover a broad range of # functionality with a single network. - +version = "master" evidence = 5 initial_height = 1000 initial_state = {initial01 = "a", initial02 = "b", initial03 = "c"} diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index dd2ad02ba..c14d4a258 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -10,6 +10,9 @@ import ( // Manifest represents a TOML testnet manifest. type Manifest struct { + // Version of the tendermint e2e-node to use + Version string `toml:"version"` + // IPv6 uses IPv6 networking instead of IPv4. Defaults to IPv4. IPv6 bool `toml:"ipv6"` diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index ad79c99c6..cdfb30f31 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -8,11 +8,14 @@ import ( "math/rand" "net" "path/filepath" + "regexp" "sort" "strconv" "strings" "time" + dbm "github.com/tendermint/tm-db" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" @@ -25,8 +28,11 @@ const ( proxyPortFirst uint32 = 5701 networkIPv4 = "10.186.73.0/24" networkIPv6 = "fd80:b10c::/48" + defaultVersion = "master" ) +var IsVersionValid = regexp.MustCompile(`master|v0.3[4-6]+.([0-9]+|x)`) + type Mode string type Protocol string type Perturbation string @@ -61,6 +67,7 @@ type Testnet struct { Name string File string Dir string + Version string IP *net.IPNet InitialHeight int64 InitialState map[string]string @@ -155,6 +162,9 @@ func LoadTestnet(file string) (*Testnet, error) { if testnet.ABCIProtocol == "" { testnet.ABCIProtocol = string(ProtocolBuiltin) } + if testnet.Version == "" { + testnet.Version = defaultVersion + } // Set up nodes, in alphabetical order (IPs and ports get same order). nodeNames := []string{} @@ -300,6 +310,9 @@ func (t Testnet) Validate() error { if len(t.Nodes) == 0 { return errors.New("network has no nodes") } + if !IsVersionValid.MatchString(t.Version) { + return fmt.Errorf("invalid network version %s", t.Version) + } switch t.KeyType { case "", types.ABCIPubKeyTypeEd25519, types.ABCIPubKeyTypeSecp256k1: default: @@ -472,6 +485,15 @@ func (n Node) Stateless() bool { return n.Mode == ModeLight || n.Mode == ModeSeed } +func (n Node) DB(name string) (dbm.DB, error) { + dbType := dbm.BackendType(n.Database) + return dbm.NewDB(name, dbType, filepath.Join(n.Dir(), "data")) +} + +func (n Node) Dir() string { + return filepath.Join(n.Testnet.Dir, n.Name) +} + // keyGenerator generates pseudorandom Ed25519 keys based on a seed. type keyGenerator struct { random *rand.Rand diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index c4a73d33f..3843e9bd5 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -353,6 +353,14 @@ Does not run any perbutations. }, }) + cli.root.AddCommand(&cobra.Command{ + Use: "upgrade", + Short: "Runs a testnet through an upgrade process from the specified version to the current version", + RunE: func(cmd *cobra.Command, args []string) error { + return Upgrade(cmd.Context(), cli.testnet, logger) + }, + }) + return cli } diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index 5f78a5b35..e8a24af66 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -162,7 +162,7 @@ services: labels: e2e: true container_name: {{ .Name }} - image: tendermint/e2e-node + image: tendermint/e2e-node:{{ $.Version }} {{- if eq .ABCIProtocol "builtin" }} entrypoint: /usr/bin/entrypoint-builtin {{- else if .LogLevel }} diff --git a/test/e2e/runner/upgrade.go b/test/e2e/runner/upgrade.go new file mode 100644 index 000000000..eb7ed0591 --- /dev/null +++ b/test/e2e/runner/upgrade.go @@ -0,0 +1,94 @@ +package main + +import ( + "context" + "fmt" + "math/rand" + "strings" + + "github.com/tendermint/tendermint/libs/log" + e2e "github.com/tendermint/tendermint/test/e2e/pkg" + + "github.com/tendermint/tendermint/scripts/keymigrate" + "github.com/tendermint/tendermint/scripts/scmigrate" +) + +func Upgrade(ctx context.Context, testnet *e2e.Testnet, logger log.Logger) error { + if err := Cleanup(logger, testnet); err != nil { + return err + } + + if err := Setup(logger, testnet); err != nil { + return err + } + + r := rand.New(rand.NewSource(randomSeed)) // nolint: gosec + + chLoadResult := make(chan error) + + lctx, loadCancel := context.WithCancel(ctx) + defer loadCancel() + go func() { + chLoadResult <- Load(lctx, logger, r, testnet) + }() + + if err := Start(ctx, logger, testnet); err != nil { + return err + } + + if err := Wait(ctx, logger, testnet, 10); err != nil { // allow some txs to go through + return err + } + + loadCancel() + if err := <-chLoadResult; err != nil { + return fmt.Errorf("transaction load failed: %w", err) + } + + // stop the network + if err := execCompose(testnet.Dir, "down"); err != nil { + return err + } + + if err := Migrate(ctx, logger, testnet); err != nil { + return err + } + + if err := Start(ctx, logger, testnet); err != nil { + return err + } + + return nil +} + +func Migrate(ctx context.Context, logger log.Logger, testnet *e2e.Testnet) error { + stores := []string{ + "tx_index", + "light", + "blockstore", + "state", + "evidence", + } + for _, node := range testnet.Nodes { + if strings.HasPrefix(oldVersion, "v0.34") && newVersion > "v0.35" { + for _, store := range stores { + db, err := node.DB(store) + if err != nil { + return fmt.Errorf("migrating db: %w", err) + } + + if err = keymigrate.Migrate(ctx, db); err != nil { + return fmt.Errorf("running migration for context %q: %w", + store, err) + } + + if store == "blockstore" { + if err := scmigrate.Migrate(ctx, db); err != nil { + return fmt.Errorf("running seen commit migration: %w", err) + } + } + } + } + } + return nil +}