diff --git a/Dockerfile-Consumer b/Dockerfile-Consumer new file mode 100644 index 0000000000..8fe2587565 --- /dev/null +++ b/Dockerfile-Consumer @@ -0,0 +1,71 @@ +# syntax=docker/dockerfile:1 + +# Consumer image to be used for compatibility tests with provider +# use docker's build argument --build-arg to overwrite the defaults +# e.g. docker build --build-arg CONSUMER_TAG=v3.1.0 +ARG CONSUMER_VERSION="latest" +ARG CONSUMER_IMAGE="cosmos-ics" + +FROM golang:1.20-alpine AS is-builder + +ENV PACKAGES curl make git libc-dev bash gcc linux-headers +RUN apk add --no-cache $PACKAGES + +ENV CGO_ENABLED=0 +ENV GOOS=linux +ENV GOFLAGS="-buildvcs=false" + +# cache go modules - done before the files are copied to allow docker to better cache +COPY go.mod /go.mod +COPY go.sum /go.sum +RUN go mod download + + +# Copy in the repo under test +ADD . /ics-consumer + +WORKDIR /ics-consumer + +# Do not specify version here. It leads to odd replacement behavior +RUN if [ -d "./cosmos-sdk" ]; then go mod edit -replace github.com/cosmos/cosmos-sdk=./cosmos-sdk; fi +RUN go mod tidy + +# Install interchain security binary +RUN make install + + +# The image from where the consumer implementation will be used +# Defaults to +FROM --platform=linux/amd64 ${CONSUMER_IMAGE}:${CONSUMER_VERSION} AS consumer + +# Get Hermes build +FROM ghcr.io/informalsystems/hermes:1.4.1 AS hermes-builder + +# Get CometMock +FROM ghcr.io/informalsystems/cometmock:v0.37.x as cometmock-builder + +# Get GoRelayer +FROM ghcr.io/informalsystems/relayer-no-gas-sim:v2.3.0-rc4-no-gas-sim AS gorelayer-builder + +FROM --platform=linux/amd64 fedora:36 +RUN dnf update -y +RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq +USER root + +COPY --from=hermes-builder /usr/bin/hermes /usr/local/bin/ +COPY --from=cometmock-builder /usr/local/bin/cometmock /usr/local/bin/cometmock +COPY --from=gorelayer-builder /bin/rly /usr/local/bin/ + +# Copy consumer from specified image +COPY --from=consumer /usr/local/bin/interchain-security-cd /usr/local/bin/interchain-security-cd +COPY --from=consumer /usr/local/bin/interchain-security-cdd /usr/local/bin/interchain-security-cdd +COPY --from=consumer /usr/local/bin/interchain-security-sd /usr/local/bin/interchain-security-sd + +# Copy provider from local build +COPY --from=is-builder /go/bin/interchain-security-pd /usr/local/bin/interchain-security-pd + +# Copy in the shell scripts that run the testnet +ADD ./tests/e2e/testnet-scripts /testnet-scripts + +# Copy in the hermes config +ADD ./tests/e2e/testnet-scripts/hermes-config.toml /root/.hermes/config.toml diff --git a/tests/e2e/builder.go b/tests/e2e/builder.go new file mode 100644 index 0000000000..0eb17e3cd2 --- /dev/null +++ b/tests/e2e/builder.go @@ -0,0 +1,84 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "path" +) + +func setupWorkspace(revision string, tmpDir string) error { + log.Printf("Setting up worktree in '%s'", tmpDir) + cmd := exec.Command("git", "worktree", "add", + "--checkout", tmpDir, revision) + cmd.Stderr = cmd.Stdout + log.Printf("Running: %s", cmd.String()) + if err := cmd.Start(); err != nil { + return err + } + if err := cmd.Wait(); err != nil { + out, _ := cmd.CombinedOutput() + log.Printf("Error creating worktree (%v): %s", err, out) + return err + } + return nil +} + +func cleanupWorkspace(workSpacePath string) error { + cmd := exec.Command("git", "worktree", "remove", workSpacePath) + cmd.Stderr = cmd.Stdout + if err := cmd.Start(); err != nil { + log.Printf("Failed removing git worktree '%s' used for docker image creation", workSpacePath) + return err + } + return cmd.Wait() +} + +// Check if docker is running +func dockerIsUp() bool { + cmd := exec.Command("docker", "info") + var errbuf bytes.Buffer + cmd.Stderr = &errbuf + if err := cmd.Run(); err != nil { + log.Printf("Docker engine is not running (%v): %s", err, errbuf.String()) + return false + } + return true +} + +// Build docker image of ICS for a given revision +func buildDockerImage(imageName string, revision string, tmpDir string) error { + log.Printf("Building ICS image for version %s", revision) + + if !dockerIsUp() { + return fmt.Errorf("docker engine is not running") + } + workSpace := path.Join(tmpDir, revision) + if err := setupWorkspace(revision, workSpace); err != nil { + return err + } + defer cleanupWorkspace(workSpace) + + _, err := os.Stat(workSpace) + if err != nil { + log.Fatalf("Worktree creation for image build failed: %v", err) + } + + log.Printf("Building docker image") + cmd := exec.Command("docker", "build", "-t", + fmt.Sprintf("cosmos-ics:%s", revision), "-f", "./Dockerfile", "./") + cmd.Dir = workSpace + //cmd.Stderr = cmd.Stdout + if err := cmd.Start(); err != nil { + log.Printf("Failed building docker image '%s': %v", revision, err) + return err + } + if err := cmd.Wait(); err != nil { + out, _ := cmd.CombinedOutput() + log.Printf("Error building image (%v): %s", err, out) + return err + } + return nil +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 462d6e4a49..d9472155f0 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -53,10 +53,11 @@ type ChainConfig struct { } type ContainerConfig struct { - ContainerName string - InstanceName string - CcvVersion string - Now time.Time + ContainerName string + InstanceName string + ConsumerVersion string + CcvVersion string + Now time.Time } // TODO: Split out TestConfig and system wide config like localsdkpath @@ -81,7 +82,9 @@ type TestConfig struct { // Used with CometMock. The time by which chains have been advanced. Used to keep chains in sync: when a new chain is started, advance its time by this value to keep chains in sync. timeOffset time.Duration - name string + // consumer version the provider should be tested against + consumerVersion string + name string } // Initialize initializes the TestConfig instance by setting the runningChains field to an empty map. @@ -397,7 +400,7 @@ func ChangeoverTestConfig() TestConfig { return tr } -func (s *TestConfig) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag string) { +func (s *TestConfig) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag string, consumerVersion string) { if localSdkPath != "" { fmt.Println("USING LOCAL SDK", localSdkPath) } @@ -405,6 +408,9 @@ func (s *TestConfig) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag fmt.Println("USING GAIA INSTEAD OF ICS provider app", gaiaTag) } + if consumerVersion != "" { + s.consumerVersion = consumerVersion + } s.useGaia = useGaia s.gaiaTag = gaiaTag s.localSdkPath = localSdkPath diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 005ea18566..5ae1f851b4 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "log" + "os" "os/exec" "reflect" "strconv" @@ -48,6 +49,8 @@ var ( gaiaTag = flag.String("gaia-tag", "", "gaia tag to use - default is latest") ) +var useConsumerVersion = flag.String("consumer-version", "", "ICS tag to specify the consumer version to test the provider against") + var ( selectedTests TestSet @@ -127,11 +130,11 @@ func executeTests(tests []testStepsWithConfig) (err error) { wg.Add(1) go func(run testStepsWithConfig) { defer wg.Done() - run.testRun.Run(run.steps, *localSdkPath, *useGaia, *gaiaTag) + run.testRun.Run(run.steps, *localSdkPath, *useGaia, *gaiaTag, *useConsumerVersion) }(testCase) } else { log.Printf("=============== running %s ===============\n", testCase.testRun.name) - testCase.testRun.Run(testCase.steps, *localSdkPath, *useGaia, *gaiaTag) + testCase.testRun.Run(testCase.steps, *localSdkPath, *useGaia, *gaiaTag, *useConsumerVersion) } } @@ -293,8 +296,8 @@ func main() { // Run sets up docker container and executes the steps in the test run. // Docker containers are torn down after the test run is complete. -func (tr *TestConfig) Run(steps []Step, localSdkPath string, useGaia bool, gaiaTag string) { - tr.SetDockerConfig(localSdkPath, useGaia, gaiaTag) +func (tr *TestConfig) Run(steps []Step, localSdkPath string, useGaia bool, gaiaTag string, consumerVersion string) { + tr.SetDockerConfig(localSdkPath, useGaia, gaiaTag, consumerVersion) tr.SetCometMockConfig(*useCometmock) tr.SetRelayerConfig(*useGorelayer) @@ -417,16 +420,40 @@ func (tr *TestConfig) executeSteps(steps []Step) { fmt.Printf("=============== finished %s tests in %v ===============\n", tr.name, time.Since(start)) } +func (tr *TestConfig) buildDockerImages() { + fmt.Printf("=============== building %s images ===============\n", tr.name) + tmpDir, err := os.MkdirTemp(os.TempDir(), "e2eWorkTree") + if err != nil { + log.Fatalf("Error createing temp directory for docker creation") + } + + // Build ICS image of a given version + if tr.consumerVersion != "" { + imageName := fmt.Sprintf("cosmos-ics:%s", tr.consumerVersion) + err := buildDockerImage(imageName, tr.consumerVersion, tmpDir) + if err != nil { + log.Fatalf("Error building docker image '%s':%v", tr.consumerVersion, err) + } + } + +} + func (tr *TestConfig) startDocker() { + tr.buildDockerImages() fmt.Printf("=============== building %s testRun ===============\n", tr.name) + + options := []string{} + localSdk := tr.localSdkPath - if localSdk == "" { - localSdk = "default" + if localSdk != "" { + options = append(options, fmt.Sprintf("-s %s", tr.localSdkPath)) } - useGaia := "false" - gaiaTag := "" + + if tr.consumerVersion != "" { + options = append(options, fmt.Sprintf("-c %s", tr.consumerVersion)) + } + if tr.useGaia { - useGaia = "true" if tr.gaiaTag != "" { majVersion, err := strconv.Atoi(tr.gaiaTag[1:strings.Index(tr.gaiaTag, ".")]) if err != nil { @@ -435,16 +462,14 @@ func (tr *TestConfig) startDocker() { if majVersion < 9 { panic(fmt.Sprintf("gaia version %s is not supported - supporting only v9.x.x and newer", tr.gaiaTag)) } - gaiaTag = tr.gaiaTag + options = append(options, fmt.Sprintf("-g %s", tr.gaiaTag)) } } scriptStr := fmt.Sprintf( - "tests/e2e/testnet-scripts/start-docker.sh %s %s %s %s %s", + "tests/e2e/testnet-scripts/start-docker.sh %s %s %s", + strings.Join(options, " "), tr.containerConfig.ContainerName, tr.containerConfig.InstanceName, - localSdk, - useGaia, - gaiaTag, ) //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. diff --git a/tests/e2e/testnet-scripts/start-docker.sh b/tests/e2e/testnet-scripts/start-docker.sh index a1d46e1f4d..2176c8b20c 100755 --- a/tests/e2e/testnet-scripts/start-docker.sh +++ b/tests/e2e/testnet-scripts/start-docker.sh @@ -2,26 +2,66 @@ # If -e is not set then if the build fails, it will use the old container, resulting in a very confusing debugging situation # Setting -e makes it error out if the build fails -set -eux +set -eux +USE_GAIA_TAG="" +CONSUMER_VERSION="" +LOCAL_SDK_PATH="" + + +usage() { + echo """ +Usage: $0 [-c ] [-g ] [-s ] [-h] container-name instance-name + [-c consumer-version] : the consumer version to be build + [-g gaia-tag] : use gaia as provider with specified version + [-s SDK-path] : use custom SDK + [-h] : print this help +""" +} + +## Process the optional arguments if any +while getopts ":c:g:s:h" flag + do + case "${flag}" in + c) CONSUMER_VERSION=${OPTARG};; + g) USE_GAIA_TAG=${OPTARG};; + s) LOCAL_SDK_PATH=${OPTARG};; + h) SHOW_HELP=1;; + *) usage;; + esac + done + +if [ ${SHOW_HELP+x} ]; then + usage + exit 0 +fi + +shift $((OPTIND - 1)) + +# Set positional arguments CONTAINER_NAME=$1 INSTANCE_NAME=$2 -LOCAL_SDK_PATH=${3:-"default"} # Sets this var to default if null or unset -USE_GAIA_PROVIDER=${4:-"false"} # if true, use gaia as provider; if false, use ICS app -USE_GAIA_TAG=${5:-""} # gaia tag to use if using gaia as provider; by default the latest tag is used + +#echo container name $CONTAINER_NAME +#echo instance name $INSTANCE_NAME +#echo LOCAL_SDK_PATH = $LOCAL_SDK_PATH +#echo CONSUMER_VERSION = $CONSUMER_VERSION +#echo GAIA_TAG = $GAIA_TAG +#echo USE_GAIA_PROVIDER = $USE_GAIA_PROVIDER +#exit -2 # Remove existing container instance set +e docker rm -f "$INSTANCE_NAME" set -e -# Delete old sdk directory if it exists +# Delete old sdk directory if it exists if [ -d "./cosmos-sdk" ]; then rm -rf ./cosmos-sdk/ -fi +fi # Copy sdk directory to working dir if path was specified -if [[ "$LOCAL_SDK_PATH" != "default" ]] +if [[ "$LOCAL_SDK_PATH" != "" ]] then cp -n -r "$LOCAL_SDK_PATH" ./cosmos-sdk printf "\n\nUsing local sdk version from %s\n\n\n" "$LOCAL_SDK_PATH" @@ -30,20 +70,23 @@ else fi # Build the Docker container -if [[ "$USE_GAIA_PROVIDER" = "true" ]] +if [[ "$USE_GAIA_TAG" != "" ]] then printf "\n\nUsing gaia as provider\n\n" printf "\n\nUsing gaia tag %s\n\n" "$USE_GAIA_TAG" docker build -f Dockerfile.gaia -t "$CONTAINER_NAME" --build-arg USE_GAIA_TAG="$USE_GAIA_TAG" . +elif [[ ! "$CONSUMER_VERSION" = "" ]]; then + printf "\n\nUsing ICS provider app as provider\n\n\n" + docker build -f Dockerfile-Consumer --build-arg CONSUMER_VERSION="${CONSUMER_VERSION}" -t "$CONTAINER_NAME" . else printf "\n\nUsing ICS provider app as provider\n\n\n" - docker build -f Dockerfile -t "$CONTAINER_NAME" . + docker build -f Dockerfile -t "$CONTAINER_NAME" . fi # Remove copied sdk directory rm -rf ./cosmos-sdk/ # Run new test container instance with extended privileges. -# Extended privileges are granted to the container here to allow for network namespace manipulation (bringing a node up/down) +# Extended privileges are granted to the container here to allow for network namespace manipulation (bringing a node up/down) # See: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities docker run --name "$INSTANCE_NAME" --cap-add=NET_ADMIN --privileged "$CONTAINER_NAME" /bin/bash /testnet-scripts/beacon.sh &