Skip to content

Commit

Permalink
[antithesis] Add test setup for xsvm
Browse files Browse the repository at this point in the history
  • Loading branch information
marun committed May 3, 2024
1 parent 099db46 commit 806a0da
Show file tree
Hide file tree
Showing 24 changed files with 698 additions and 128 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ jobs:
- name: Check image build
shell: bash
run: bash -x scripts/tests.build_image.sh
test_build_antithesis_avalanchego_image:
name: Antithesis avalanchego build
test_build_antithesis_images:
name: Build Antithesis images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -275,6 +275,11 @@ jobs:
run: bash -x scripts/tests.build_antithesis_images.sh
env:
TEST_SETUP: avalanchego
- name: Check image build for xsvm test setup
shell: bash
run: bash -x scripts/tests.build_antithesis_images.sh
env:
TEST_SETUP: xsvm
govulncheck:
runs-on: ubuntu-latest
name: govulncheck
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/publish_antithesis_images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@ jobs:
IMAGE_PREFIX: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
TAG: latest
TEST_SETUP: avalanchego

- name: Build images for xsvm test setup
run: bash -x ./scripts/build_antithesis_images.sh
env:
IMAGE_PREFIX: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}
TAG: latest
TEST_SETUP: xsvm
32 changes: 26 additions & 6 deletions scripts/build_antithesis_images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ set -euo pipefail
# Builds docker images for antithesis testing.

# e.g.,
# ./scripts/build_antithesis_images.sh # Build local images
# IMAGE_PREFIX=<registry>/<repo> TAG=latest ./scripts/build_antithesis_images.sh # Specify a prefix to enable image push and use a specific tag
# TEST_SETUP=avalanchego ./scripts/build_antithesis_images.sh # Build local images for avalanchego
# TEST_SETUP=xsvm ./scripts/build_antithesis_images.sh # Build local images for xsvm
# TEST_SETUP=xsvm IMAGE_PREFIX=<registry>/<repo> TAG=latest ./scripts/build_antithesis_images.sh # Specify a prefix to enable image push and use a specific tag

# Directory above this script
AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
Expand All @@ -28,11 +29,14 @@ GO_VERSION="$(go list -m -f '{{.GoVersion}}')"
function build_images {
local test_setup=$1
local uninstrumented_node_dockerfile=$2
local node_only=${3:-}

# Define image names
local base_image_name="antithesis-${test_setup}"
local avalanchego_node_image_name="antithesis-avalanchego-node:${TAG}"
if [[ -n "${IMAGE_PREFIX}" ]]; then
base_image_name="${IMAGE_PREFIX}/${base_image_name}"
avalanchego_node_image_name="${IMAGE_PREFIX}/${avalanchego_node_image_name}"
fi
local node_image_name="${base_image_name}-node:${TAG}"
local workload_image_name="${base_image_name}-workload:${TAG}"
Expand All @@ -49,18 +53,34 @@ function build_images {
fi

# Define default build command
local docker_cmd="docker buildx build --build-arg GO_VERSION=${GO_VERSION}"
local docker_cmd="docker buildx build --build-arg GO_VERSION=${GO_VERSION} --build-arg NODE_IMAGE=${node_image_name}"

if [[ "${test_setup}" == "xsvm" ]]; then
# The xsvm node image is build on the avalanchego node image
docker_cmd="${docker_cmd} --build-arg AVALANCHEGO_NODE_IMAGE=${avalanchego_node_image_name}"
fi

# Build node image first to allow the config and workload image builds to use it.
${docker_cmd} -t "${node_image_name}" -f "${node_dockerfile}" "${AVALANCHE_PATH}"
${docker_cmd} --build-arg NODE_IMAGE="${node_image_name}" -t "${workload_image_name}" -f "${base_dockerfile}.workload" "${AVALANCHE_PATH}"
${docker_cmd} --build-arg IMAGE_TAG="${TAG}" -t "${config_image_name}" -f "${base_dockerfile}.config" "${AVALANCHE_PATH}"

if [[ -z "${node_only}" || "${node_only}" == "0" ]]; then
# Skip building the config and workload images. Supports building the avalanchego
# node image as the base image for the xsvm node image.
${docker_cmd} --build-arg IMAGE_TAG="${TAG}" -t "${config_image_name}" -f "${base_dockerfile}.config" "${AVALANCHE_PATH}"
${docker_cmd} -t "${workload_image_name}" -f "${base_dockerfile}.workload" "${AVALANCHE_PATH}"
fi
}

TEST_SETUP="${TEST_SETUP:-}"
if [[ "${TEST_SETUP}" == "avalanchego" ]]; then
build_images avalanchego "${AVALANCHE_PATH}/Dockerfile"
elif [[ "${TEST_SETUP}" == "xsvm" ]]; then
# Only build the node image to use as the base for the xsvm image
NODE_ONLY=1
build_images avalanchego "${AVALANCHE_PATH}/Dockerfile" "${NODE_ONLY}"

build_images xsvm "${AVALANCHE_PATH}/vms/example/xsvm/Dockerfile"
else
echo "TEST_SETUP must be set. Valid values are 'avalanchego'"
echo "TEST_SETUP must be set. Valid values are 'avalanchego' or 'xsvm'"
exit 255
fi
11 changes: 11 additions & 0 deletions scripts/build_antithesis_xsvm_workload.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

set -euo pipefail

# Directory above this script
AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
# Load the constants
source "$AVALANCHE_PATH"/scripts/constants.sh

echo "Building Workload..."
go build -o "$AVALANCHE_PATH/build/antithesis-xsvm-workload" "$AVALANCHE_PATH/tests/antithesis/xsvm/"*.go
17 changes: 14 additions & 3 deletions scripts/tests.build_antithesis_images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ set -euo pipefail
# 4. Stopping the workload and its target network
#

# e.g.,
# TEST_SETUP=avalanchego ./scripts/tests.build_antithesis_images.sh # Test build of images for avalanchego test setup
# DEBUG=1 TEST_SETUP=avalanchego ./scripts/tests.build_antithesis_images.sh # Do not detach the compose session to see output

AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )

# Discover the default tag that will be used for the image
Expand All @@ -36,8 +40,10 @@ function cleanup {
docker rm "${CONTAINER_NAME}"
echo "stopping and removing the docker compose project"
${COMPOSE_CMD} down --volumes
echo "removing temporary dir"
rm -rf "${TMPDIR}"
if [[ -z "${DEBUG:-}" ]]; then
echo "removing temporary dir"
rm -rf "${TMPDIR}"
fi
}
trap cleanup EXIT

Expand All @@ -48,7 +54,12 @@ docker cp "${CONTAINER_NAME}":/docker-compose.yml "${COMPOSE_FILE}"
docker cp "${CONTAINER_NAME}":/volumes "${TMPDIR}/"

# Run the docker compose project for one minute without error
${COMPOSE_CMD} up -d
COMPOSE_UP_ARGS=
if [[ -z "${DEBUG:-}" ]]; then
COMPOSE_UP_ARGS="-d"
fi

${COMPOSE_CMD} up "${COMPOSE_UP_ARGS}"
sleep 60
if ${COMPOSE_CMD} ps -q | xargs docker inspect -f '{{ .State.Status }}' | grep -v 'running'; then
echo "An error occurred."
Expand Down
13 changes: 8 additions & 5 deletions tests/antithesis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ enables discovery and reproduction of anomalous behavior.

## Package details

| Filename | Purpose |
|:-------------|:----------------------------------------------------------------------------------|
| compose.go | Enables generation of Docker Compose project files for antithesis testing. |
| avalanchego/ | Contains resources supporting antithesis testing of avalanchego's primary chains. |

| Filename | Purpose |
|:---------------|:--------------------------------------------------------------------------------|
| compose.go | Generates Docker Compose project files and volume paths for antithesis testing. |
| config.go | Defines common flags for the workload binary. |
| init_db.go | Initializes db state for subnet testing. |
| node_health.go | Helper to check node health. |
| avalanchego/ | Defines an antithesis test setup for avalanchego's primary chains. |
| xsvm/ | Defines an antithesis test setup for the xsvm VM. |

## Instrumentation

Expand Down
3 changes: 3 additions & 0 deletions tests/antithesis/avalanchego/Dockerfile.node
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ RUN mkdir -p /symbols
COPY --from=builder /avalanchego_instrumented/symbols /symbols
COPY --from=builder /opt/antithesis/lib/libvoidstar.so /usr/lib/libvoidstar.so

# Use the same path as the uninstrumented node image for consistency
WORKDIR /avalanchego/build

# Copy the executable into the container
COPY --from=builder /avalanchego_instrumented/customer/build/avalanchego ./avalanchego

Expand Down
39 changes: 3 additions & 36 deletions tests/antithesis/avalanchego/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
"os"
"time"

"github.com/ava-labs/avalanchego/api/health"
"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/choices"
"github.com/ava-labs/avalanchego/tests/antithesis"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
"github.com/ava-labs/avalanchego/utils/set"
Expand All @@ -38,13 +38,13 @@ import (
const NumKeys = 5

func main() {
c, err := NewConfig(os.Args)
c, err := antithesis.NewConfig(os.Args)
if err != nil {
log.Fatalf("invalid config: %s", err)
}

ctx := context.Background()
awaitHealthyNodes(ctx, c.URIs)
antithesis.AwaitHealthyNodes(ctx, c.URIs)

kc := secp256k1fx.NewKeychain(genesis.EWOQKey)
walletSyncStartTime := time.Now()
Expand Down Expand Up @@ -133,39 +133,6 @@ func main() {
genesisWorkload.run(ctx)
}

func awaitHealthyNodes(ctx context.Context, uris []string) {
for _, uri := range uris {
awaitHealthyNode(ctx, uri)
}
log.Println("all nodes reported healthy")
}

func awaitHealthyNode(ctx context.Context, uri string) {
client := health.NewClient(uri)
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()

log.Printf("awaiting node health at %s", uri)
for {
res, err := client.Health(ctx, nil)
switch {
case err != nil:
log.Printf("node couldn't be reached at %s", uri)
case res.Healthy:
log.Printf("node reported healthy at %s", uri)
return
default:
log.Printf("node reported unhealthy at %s", uri)
}

select {
case <-ticker.C:
case <-ctx.Done():
log.Printf("node health check cancelled at %s", uri)
}
}
}

type workload struct {
id int
wallet primary.Wallet
Expand Down
63 changes: 49 additions & 14 deletions tests/antithesis/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,34 @@ func newComposeProject(network *tmpnet.Network, nodeImageName string, workloadIm
config.StakingSignerKeyContentKey: signerKey,
}

nodeName := "avalanche"
serviceName := getServiceName(i)

volumes := []types.ServiceVolumeConfig{
{
Type: types.VolumeTypeBind,
Source: fmt.Sprintf("./volumes/%s/logs", serviceName),
Target: "/root/.avalanchego/logs",
},
}

trackSubnets, err := node.Flags.GetStringVal(config.TrackSubnetsKey)
if err != nil {
return nil, err
}
if len(trackSubnets) > 0 {
env[config.TrackSubnetsKey] = trackSubnets
// DB volume will need to initialized with the subnet
volumes = append(volumes, types.ServiceVolumeConfig{
Type: types.VolumeTypeBind,
Source: fmt.Sprintf("./volumes/%s/db", serviceName),
Target: "/root/.avalanchego/db",
})
}

if i == 0 {
nodeName += "-bootstrap-node"
bootstrapIP = address + ":9651"
bootstrapIDs = node.NodeID.String()
} else {
nodeName = fmt.Sprintf("%s-node-%d", nodeName, i+1)
env[config.BootstrapIPsKey] = bootstrapIP
env[config.BootstrapIDsKey] = bootstrapIDs
}
Expand All @@ -120,18 +141,12 @@ func newComposeProject(network *tmpnet.Network, nodeImageName string, workloadIm
env = keyMapToEnvVarMap(env)

services[i+1] = types.ServiceConfig{
Name: nodeName,
ContainerName: nodeName,
Hostname: nodeName,
Name: serviceName,
ContainerName: serviceName,
Hostname: serviceName,
Image: nodeImageName,
Volumes: []types.ServiceVolumeConfig{
{
Type: types.VolumeTypeBind,
Source: fmt.Sprintf("./volumes/%s/logs", nodeName),
Target: "/root/.avalanchego/logs",
},
},
Environment: env.ToMappingWithEquals(),
Volumes: volumes,
Environment: env.ToMappingWithEquals(),
Networks: map[string]*types.ServiceNetworkConfig{
networkName: {
Ipv4Address: address,
Expand All @@ -146,6 +161,15 @@ func newComposeProject(network *tmpnet.Network, nodeImageName string, workloadIm
workloadEnv := types.Mapping{
"AVAWL_URIS": strings.Join(uris, " "),
}
chainIDs := []string{}
for _, subnet := range network.Subnets {
for _, chain := range subnet.Chains {
chainIDs = append(chainIDs, chain.ChainID.String())
}
}
if len(chainIDs) > 0 {
workloadEnv["AVAWL_CHAIN_IDs"] = strings.Join(chainIDs, " ")
}

workloadName := "workload"
services[0] = types.ServiceConfig{
Expand Down Expand Up @@ -188,3 +212,14 @@ func keyMapToEnvVarMap(keyMap types.Mapping) types.Mapping {
}
return envVarMap
}

// Retrieve the service name for a node at the given index. Common to
// GenerateComposeConfig and InitDBVolumes to ensure consistency
// between db volumes configuration and volume paths.
func getServiceName(index int) string {
baseName := "avalanche"
if index == 0 {
return baseName + "-bootstrap-node"
}
return fmt.Sprintf("%s-node-%d", baseName, index+1)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package main
package antithesis

import (
"errors"
Expand All @@ -15,7 +15,8 @@ import (
)

const (
URIsKey = "uris"
URIsKey = "uris"
ChainIDsKey = "chain-ids"

FlagsName = "workload"
EnvPrefix = "avawl"
Expand All @@ -27,7 +28,8 @@ var (
)

type Config struct {
URIs []string
URIs []string
ChainIDs []string
}

func NewConfig(arguments []string) (*Config, error) {
Expand All @@ -37,7 +39,8 @@ func NewConfig(arguments []string) (*Config, error) {
}

c := &Config{
URIs: v.GetStringSlice(URIsKey),
URIs: v.GetStringSlice(URIsKey),
ChainIDs: v.GetStringSlice(ChainIDsKey),
}
return c, c.Verify()
}
Expand All @@ -56,6 +59,7 @@ func parseFlags(arguments []string) (*viper.Viper, error) {

fs := pflag.NewFlagSet(FlagsName, pflag.ContinueOnError)
fs.StringSlice(URIsKey, []string{primary.LocalAPIURI}, "URIs of nodes that the workload can communicate with")
fs.StringSlice(ChainIDsKey, []string{}, "IDs of chains to target for testing")
if err := fs.Parse(arguments[1:]); err != nil {
return nil, fmt.Errorf("failed parsing CLI flags: %w", err)
}
Expand Down
Loading

0 comments on commit 806a0da

Please sign in to comment.