From e0ccaf3e1586669507ecfb6a331d8f3fcc7349ed Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Mon, 1 Jul 2024 08:15:49 +0200 Subject: [PATCH 01/11] [testing] Add a job to test state sync bootstrap of testnet --- .github/actionlint.yml | 2 + .github/actions/check-bootstrap/action.yml | 48 ++++++++ .../check-bootstrap-testnet-state-sync.yml | 26 +++++ tests/bootstrap/main.go | 106 ++++++++++++++++++ tests/fixture/tmpnet/defaults.go | 13 ++- tests/fixture/tmpnet/network.go | 6 +- tests/fixture/tmpnet/node.go | 10 +- tests/fixture/tmpnet/utils.go | 7 +- 8 files changed, 211 insertions(+), 7 deletions(-) create mode 100644 .github/actions/check-bootstrap/action.yml create mode 100644 .github/workflows/check-bootstrap-testnet-state-sync.yml create mode 100644 tests/bootstrap/main.go diff --git a/.github/actionlint.yml b/.github/actionlint.yml index 62bee747e6f..2d3f1a35ef0 100644 --- a/.github/actionlint.yml +++ b/.github/actionlint.yml @@ -2,3 +2,5 @@ self-hosted-runner: labels: - custom-arm64-focal - custom-arm64-jammy + - net-outage-sim + - avalanche-avalanchego diff --git a/.github/actions/check-bootstrap/action.yml b/.github/actions/check-bootstrap/action.yml new file mode 100644 index 00000000000..3a4376989e7 --- /dev/null +++ b/.github/actions/check-bootstrap/action.yml @@ -0,0 +1,48 @@ +name: 'Check bootstrap for a network and state sync configuration' +description: 'Checks that bootstrap is possible for the given network and state sync configuration' + +inputs: + network_id: + required: true + state_sync_enabled: + required: true + prometheus_id: + required: true + prometheus_password: + required: true + loki_id: + required: true + loki_password: + required: true + +runs: + using: composite + steps: + - name: Setup Go + uses: ./.github/actions/setup-go-for-project + + - name: Build AvalancheGo Binary + shell: bash + run: ./scripts/build.sh -r + + - name: Check avalanchego version + shell: bash + run: ./build/avalanchego --version + + - name: Run bootstrap for testnet with state-sync + uses: ./.github/actions/run-monitored-tmpnet-cmd + with: + run: go run ./tests/bootstrap --avalanchego-path=./build/avalanchego --network-id=${{ inputs.network_id }} --state-sync-enabled=${{ inputs.state_sync_enabled }} + prometheus_id: ${{ inputs.prometheus_id }} + prometheus_password: ${{ inputs.prometheus_password }} + loki_id: ${{ inputs.loki_id }} + loki_password: ${{ inputs.loki_password }} + + # Skip creation of an artifact in favor of log collection to loki + + - name: Check size of tmpnet path + if: always() + shell: bash + run: | + echo "Checking tmpnet disk usage:" + du -sh ~/.tmpnet diff --git a/.github/workflows/check-bootstrap-testnet-state-sync.yml b/.github/workflows/check-bootstrap-testnet-state-sync.yml new file mode 100644 index 00000000000..802f64c5362 --- /dev/null +++ b/.github/workflows/check-bootstrap-testnet-state-sync.yml @@ -0,0 +1,26 @@ +name: 'Check Bootstrap (testnet,state-sync)' + +on: + # TODO(marun) Add a schedule + workflow_dispatch: + + # TODO(marun) For testing only - remove before merge + pull_request: + +jobs: + check_bootstrap_testnet_state_sync: + name: Check Bootstrap (testnet,state-sync) + runs-on: avalanche-avalanchego + timeout-minutes: 4320 # 3 days + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Check bootstrap (testnet,state-sync) + uses: ./.github/actions/check-bootstrap + with: + network_id: 5 # testnet + state_sync_enabled: true + prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} + prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }} + loki_id: ${{ secrets.LOKI_ID || '' }} + loki_password: ${{ secrets.LOKI_PASSWORD || '' }} diff --git a/tests/bootstrap/main.go b/tests/bootstrap/main.go new file mode 100644 index 00000000000..289a7846b4c --- /dev/null +++ b/tests/bootstrap/main.go @@ -0,0 +1,106 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "time" + + "github.com/google/uuid" + + "github.com/ava-labs/avalanchego/config" + "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" + "github.com/ava-labs/avalanchego/utils/logging" +) + +// Simple test that starts a single node and waits for it to finish bootstrapping. + +func main() { + avalanchegoPath := flag.String("avalanchego-path", "", "The path to an avalanchego binary") + networkID := flag.Int64("network-id", 0, "The ID of the network to bootstrap from") + stateSyncEnabled := flag.Bool("state-sync-enabled", false, "Whether state syncing should be enabled") + maxDuration := flag.Duration("max-duration", time.Hour*72, "The maximum duration the network should run for") + + flag.Parse() + + if len(*avalanchegoPath) == 0 { + log.Fatal("avalanchego-path is required") + } + if *networkID == 0 { + log.Fatal("network-id is required") + } + if *maxDuration == 0 { + log.Fatal("max-duration is required") + } + + if err := checkBootstrap(*avalanchegoPath, uint32(*networkID), *stateSyncEnabled, *maxDuration); err != nil { + log.Fatalf("Failed to check bootstrap: %v\n", err) + } +} + +func checkBootstrap(avalanchegoPath string, networkID uint32, stateSyncEnabled bool, maxDuration time.Duration) error { + flags := tmpnet.DefaultLocalhostFlags() + flags.SetDefaults(tmpnet.FlagsMap{ + config.HealthCheckFreqKey: "30s", + // Minimize logging overhead + config.LogDisplayLevelKey: logging.Off.String(), + config.LogLevelKey: logging.Info.String(), + }) + + // Create a new single-node network that will bootstrap from the specified network + network := &tmpnet.Network{ + UUID: uuid.NewString(), + NetworkID: networkID, + Owner: "bootstrap-test", + Nodes: tmpnet.NewNodesOrPanic(1), + DefaultFlags: flags, + DefaultRuntimeConfig: tmpnet.NodeRuntimeConfig{ + // TODO(marun) Rename AvalancheGoPath to AvalanchegoPath + AvalancheGoPath: avalanchegoPath, + }, + ChainConfigs: map[string]tmpnet.FlagsMap{ + "C": { + "state-sync-enabled": stateSyncEnabled, + }, + }, + } + + if err := network.Create(""); err != nil { + return fmt.Errorf("failed to create network: %w", err) + } + node := network.Nodes[0] + + log.Printf("Starting node in path %s (UUID: %s)\n", network.Dir, network.UUID) + + ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout) + defer cancel() + if err := network.StartNode(ctx, os.Stdout, node); err != nil { + return fmt.Errorf("failed to start node: %w", err) + } + defer func() { + ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout) + defer cancel() + if err := node.Stop(ctx); err != nil { + log.Printf("Failed to stop node: %v\n", err) + } + }() + + log.Printf("Metrics: %s\n", tmpnet.DefaultMetricsLink(network.UUID, time.Now())) + + log.Print("Waiting for node to indicate bootstrap complete by reporting healthy\n") + + // Avoid checking too often to avoid log spam + healthCheckInterval := 1 * time.Minute + + ctx, cancel = context.WithTimeout(context.Background(), maxDuration) + defer cancel() + if err := tmpnet.WaitForHealthyWithInterval(ctx, node, healthCheckInterval); err != nil { + return fmt.Errorf("node failed to become healthy before timeout: %w", err) + } + return nil +} diff --git a/tests/fixture/tmpnet/defaults.go b/tests/fixture/tmpnet/defaults.go index c5dbfeeebc9..4c19ea08b11 100644 --- a/tests/fixture/tmpnet/defaults.go +++ b/tests/fixture/tmpnet/defaults.go @@ -46,20 +46,27 @@ func DefaultTestFlags() FlagsMap { } } +// Flags appropriate for networks that aren't intended to be publicly accessible. +func DefaultLocalhostFlags() FlagsMap { + return FlagsMap{ + config.PublicIPKey: "127.0.0.1", + config.HTTPHostKey: "127.0.0.1", + config.StakingHostKey: "127.0.0.1", + } +} + // Flags appropriate for tmpnet networks. func DefaultTmpnetFlags() FlagsMap { // Supply only non-default configuration to ensure that default values will be used. flags := FlagsMap{ // Specific to tmpnet deployment - config.PublicIPKey: "127.0.0.1", - config.HTTPHostKey: "127.0.0.1", - config.StakingHostKey: "127.0.0.1", config.LogDisplayLevelKey: logging.Off.String(), // Display logging not needed since nodes run headless config.LogLevelKey: logging.Debug.String(), // Specific to e2e testing config.MinStakeDurationKey: DefaultMinStakeDuration.String(), config.ProposerVMUseCurrentHeightKey: true, } + flags.SetDefaults(DefaultLocalhostFlags()) flags.SetDefaults(DefaultTestFlags()) return flags } diff --git a/tests/fixture/tmpnet/network.go b/tests/fixture/tmpnet/network.go index 3eec5cdf7a0..2bc83e8bb15 100644 --- a/tests/fixture/tmpnet/network.go +++ b/tests/fixture/tmpnet/network.go @@ -373,7 +373,7 @@ func (n *Network) StartNodes(ctx context.Context, w io.Writer, nodesToStart ...* return err } // Provide a link to the main dashboard filtered by the uuid and showing results from now till whenever the link is viewed - if _, err := fmt.Fprintf(w, "\nMetrics: https://grafana-poc.avax-dev.network/d/kBQpRdWnk/avalanche-main-dashboard?&var-filter=network_uuid%%7C%%3D%%7C%s&var-filter=is_ephemeral_node%%7C%%3D%%7Cfalse&from=%d&to=now\n", n.UUID, startTime.UnixMilli()); err != nil { + if _, err := fmt.Fprintf(w, "\nMetrics: %s\n", DefaultMetricsLink(n.UUID, startTime)); err != nil { return err } @@ -1009,3 +1009,7 @@ func getRPCVersion(command string, versionArgs ...string) (uint64, error) { return version.RPCChainVM, nil } + +func DefaultMetricsLink(uuid string, startTime time.Time) string { + return fmt.Sprintf("https://grafana-poc.avax-dev.network/d/kBQpRdWnk/avalanche-main-dashboard?&var-filter=network_uuid%%7C%%3D%%7C%s&var-filter=is_ephemeral_node%%7C%%3D%%7Cfalse&from=%d&to=now", uuid, startTime.UnixMilli()) +} diff --git a/tests/fixture/tmpnet/node.go b/tests/fixture/tmpnet/node.go index 3a6076af128..cbd4878433a 100644 --- a/tests/fixture/tmpnet/node.go +++ b/tests/fixture/tmpnet/node.go @@ -236,8 +236,14 @@ func (n *Node) SetNetworkingConfig(bootstrapIDs []string, bootstrapIPs []string) // Default to dynamic port allocation n.Flags[config.StakingPortKey] = 0 } - n.Flags[config.BootstrapIDsKey] = strings.Join(bootstrapIDs, ",") - n.Flags[config.BootstrapIPsKey] = strings.Join(bootstrapIPs, ",") + if len(bootstrapIDs) == 0 { + // bootstrap-* should not be provided if bootstrapping from mainnet or testnet + delete(n.Flags, config.BootstrapIDsKey) + delete(n.Flags, config.BootstrapIPsKey) + } else { + n.Flags[config.BootstrapIDsKey] = strings.Join(bootstrapIDs, ",") + n.Flags[config.BootstrapIPsKey] = strings.Join(bootstrapIPs, ",") + } } // Ensures staking and signing keys are generated if not already present and diff --git a/tests/fixture/tmpnet/utils.go b/tests/fixture/tmpnet/utils.go index ea320f2a880..5f4abb628d4 100644 --- a/tests/fixture/tmpnet/utils.go +++ b/tests/fixture/tmpnet/utils.go @@ -19,10 +19,15 @@ const ( // WaitForHealthy blocks until Node.IsHealthy returns true or an error (including context timeout) is observed. func WaitForHealthy(ctx context.Context, node *Node) error { + return WaitForHealthyWithInterval(ctx, node, DefaultNodeTickerInterval) +} + +// WaitForHealthy blocks until Node.IsHealthy returns true or an error (including context timeout) is observed. +func WaitForHealthyWithInterval(ctx context.Context, node *Node, interval time.Duration) error { if _, ok := ctx.Deadline(); !ok { return fmt.Errorf("unable to wait for health for node %q with a context without a deadline", node.NodeID) } - ticker := time.NewTicker(DefaultNodeTickerInterval) + ticker := time.NewTicker(interval) defer ticker.Stop() for { From bd0044f35e0797682d1fee8db555ea9432966003 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Thu, 11 Jul 2024 11:42:48 -0700 Subject: [PATCH 02/11] fixup: Add additional jobs --- .../check-bootstrap-mainnet-full-sync.yml | 23 ++++++++++++++++ .../check-bootstrap-mainnet-state-sync.yml | 23 ++++++++++++++++ .../check-bootstrap-testnet-full-sync.yml | 26 +++++++++++++++++++ .../check-bootstrap-testnet-state-sync.yml | 3 --- tests/bootstrap/main.go | 5 +++- 5 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/check-bootstrap-mainnet-full-sync.yml create mode 100644 .github/workflows/check-bootstrap-mainnet-state-sync.yml create mode 100644 .github/workflows/check-bootstrap-testnet-full-sync.yml diff --git a/.github/workflows/check-bootstrap-mainnet-full-sync.yml b/.github/workflows/check-bootstrap-mainnet-full-sync.yml new file mode 100644 index 00000000000..df92798a006 --- /dev/null +++ b/.github/workflows/check-bootstrap-mainnet-full-sync.yml @@ -0,0 +1,23 @@ +name: 'Check Bootstrap (mainnet,full-sync)' + +on: + # TODO(marun) Add a schedule + workflow_dispatch: + +jobs: + check_bootstrap_mainnet_full_sync: + name: Check Bootstrap (mainnet,full-sync) + runs-on: avalanche-avalanchego + timeout-minutes: 4320 # 3 days + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Check bootstrap (mainnet,full-sync) + uses: ./.github/actions/check-bootstrap + with: + network_id: 1 # mainnet + state_sync_enabled: false + prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} + prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }} + loki_id: ${{ secrets.LOKI_ID || '' }} + loki_password: ${{ secrets.LOKI_PASSWORD || '' }} diff --git a/.github/workflows/check-bootstrap-mainnet-state-sync.yml b/.github/workflows/check-bootstrap-mainnet-state-sync.yml new file mode 100644 index 00000000000..0d0f34e068a --- /dev/null +++ b/.github/workflows/check-bootstrap-mainnet-state-sync.yml @@ -0,0 +1,23 @@ +name: 'Check Bootstrap (mainnet,state-sync)' + +on: + # TODO(marun) Add a schedule + workflow_dispatch: + +jobs: + check_bootstrap_mainnet_state_sync: + name: Check Bootstrap (mainnet,state-sync) + runs-on: avalanche-avalanchego + timeout-minutes: 4320 # 3 days + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Check bootstrap (mainnet,state-sync) + uses: ./.github/actions/check-bootstrap + with: + network_id: 1 # mainnet + state_sync_enabled: true + prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} + prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }} + loki_id: ${{ secrets.LOKI_ID || '' }} + loki_password: ${{ secrets.LOKI_PASSWORD || '' }} diff --git a/.github/workflows/check-bootstrap-testnet-full-sync.yml b/.github/workflows/check-bootstrap-testnet-full-sync.yml new file mode 100644 index 00000000000..f83221c7f07 --- /dev/null +++ b/.github/workflows/check-bootstrap-testnet-full-sync.yml @@ -0,0 +1,26 @@ +name: 'Check Bootstrap (testnet,full-sync)' + +on: + # TODO(marun) Add a schedule + workflow_dispatch: + + # TODO(marun) For testing only - remove before merge + pull_request: + +jobs: + check_bootstrap_testnet_full_sync: + name: Check Bootstrap (testnet,full-sync) + runs-on: avalanche-avalanchego + timeout-minutes: 4320 # 3 days + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Check bootstrap (testnet,full-sync) + uses: ./.github/actions/check-bootstrap + with: + network_id: 5 # testnet + state_sync_enabled: false + prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} + prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }} + loki_id: ${{ secrets.LOKI_ID || '' }} + loki_password: ${{ secrets.LOKI_PASSWORD || '' }} diff --git a/.github/workflows/check-bootstrap-testnet-state-sync.yml b/.github/workflows/check-bootstrap-testnet-state-sync.yml index 802f64c5362..55c108df940 100644 --- a/.github/workflows/check-bootstrap-testnet-state-sync.yml +++ b/.github/workflows/check-bootstrap-testnet-state-sync.yml @@ -4,9 +4,6 @@ on: # TODO(marun) Add a schedule workflow_dispatch: - # TODO(marun) For testing only - remove before merge - pull_request: - jobs: check_bootstrap_testnet_state_sync: name: Check Bootstrap (testnet,state-sync) diff --git a/tests/bootstrap/main.go b/tests/bootstrap/main.go index 289a7846b4c..c6dee4f6c4b 100644 --- a/tests/bootstrap/main.go +++ b/tests/bootstrap/main.go @@ -94,7 +94,7 @@ func checkBootstrap(avalanchegoPath string, networkID uint32, stateSyncEnabled b log.Print("Waiting for node to indicate bootstrap complete by reporting healthy\n") - // Avoid checking too often to avoid log spam + // Avoid checking too often to prevent log spam healthCheckInterval := 1 * time.Minute ctx, cancel = context.WithTimeout(context.Background(), maxDuration) @@ -102,5 +102,8 @@ func checkBootstrap(avalanchegoPath string, networkID uint32, stateSyncEnabled b if err := tmpnet.WaitForHealthyWithInterval(ctx, node, healthCheckInterval); err != nil { return fmt.Errorf("node failed to become healthy before timeout: %w", err) } + + log.Print("Bootstrap completed successfully!\n") + return nil } From 211a177532577b81a669daa1a0c48409e98ecb19 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Mon, 15 Jul 2024 09:49:56 -0700 Subject: [PATCH 03/11] fixup: Remove time limit for testnet full sync --- .github/workflows/check-bootstrap-testnet-full-sync.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check-bootstrap-testnet-full-sync.yml b/.github/workflows/check-bootstrap-testnet-full-sync.yml index f83221c7f07..f2f8ff3c643 100644 --- a/.github/workflows/check-bootstrap-testnet-full-sync.yml +++ b/.github/workflows/check-bootstrap-testnet-full-sync.yml @@ -11,7 +11,6 @@ jobs: check_bootstrap_testnet_full_sync: name: Check Bootstrap (testnet,full-sync) runs-on: avalanche-avalanchego - timeout-minutes: 4320 # 3 days steps: - name: Checkout Repository uses: actions/checkout@v4 From c49d8bb3648942943a0bbb31e61ef514f59d1175 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Mon, 15 Jul 2024 19:13:52 -0700 Subject: [PATCH 04/11] fixup: Ensure maximum timeout for testnet full sync --- .github/workflows/check-bootstrap-testnet-full-sync.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-bootstrap-testnet-full-sync.yml b/.github/workflows/check-bootstrap-testnet-full-sync.yml index f2f8ff3c643..e0c98475d2b 100644 --- a/.github/workflows/check-bootstrap-testnet-full-sync.yml +++ b/.github/workflows/check-bootstrap-testnet-full-sync.yml @@ -11,6 +11,7 @@ jobs: check_bootstrap_testnet_full_sync: name: Check Bootstrap (testnet,full-sync) runs-on: avalanche-avalanchego + timeout-minutes: 7200 # 5 days, maximum allowed for jobs running on a self-hosted runner steps: - name: Checkout Repository uses: actions/checkout@v4 From 758e5b753bf5f6ba30cdf8406c538a08c80387bc Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Mon, 15 Jul 2024 19:19:02 -0700 Subject: [PATCH 05/11] fixup: Increase timeout for mainnet jobs --- .github/workflows/check-bootstrap-mainnet-full-sync.yml | 2 +- .github/workflows/check-bootstrap-mainnet-state-sync.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-bootstrap-mainnet-full-sync.yml b/.github/workflows/check-bootstrap-mainnet-full-sync.yml index df92798a006..3063a82bbf5 100644 --- a/.github/workflows/check-bootstrap-mainnet-full-sync.yml +++ b/.github/workflows/check-bootstrap-mainnet-full-sync.yml @@ -8,7 +8,7 @@ jobs: check_bootstrap_mainnet_full_sync: name: Check Bootstrap (mainnet,full-sync) runs-on: avalanche-avalanchego - timeout-minutes: 4320 # 3 days + timeout-minutes: 7200 # 5 days, maximum allowed for jobs running on a self-hosted runner steps: - name: Checkout Repository uses: actions/checkout@v4 diff --git a/.github/workflows/check-bootstrap-mainnet-state-sync.yml b/.github/workflows/check-bootstrap-mainnet-state-sync.yml index 0d0f34e068a..5fc4d771a3c 100644 --- a/.github/workflows/check-bootstrap-mainnet-state-sync.yml +++ b/.github/workflows/check-bootstrap-mainnet-state-sync.yml @@ -8,7 +8,7 @@ jobs: check_bootstrap_mainnet_state_sync: name: Check Bootstrap (mainnet,state-sync) runs-on: avalanche-avalanchego - timeout-minutes: 4320 # 3 days + timeout-minutes: 7200 # 5 days, maximum allowed for jobs running on a self-hosted runner steps: - name: Checkout Repository uses: actions/checkout@v4 From 6440b1dded5a25700f9401135da63ef52ecd6e4c Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Tue, 23 Jul 2024 17:34:46 -0700 Subject: [PATCH 06/11] fixup: Add a docker image for the bootstrap tester --- .../check-bootstrap-testnet-full-sync.yml | 3 -- .github/workflows/publish_docker_image.yml | 6 +++ scripts/build_bootstrap_tester.sh | 13 +++++ scripts/build_bootstrap_tester_image.sh | 52 +++++++++++++++++++ tests/bootstrap/Dockerfile | 34 ++++++++++++ tests/bootstrap/main.go | 31 +++++++++-- 6 files changed, 131 insertions(+), 8 deletions(-) create mode 100755 scripts/build_bootstrap_tester.sh create mode 100755 scripts/build_bootstrap_tester_image.sh create mode 100644 tests/bootstrap/Dockerfile diff --git a/.github/workflows/check-bootstrap-testnet-full-sync.yml b/.github/workflows/check-bootstrap-testnet-full-sync.yml index e0c98475d2b..663a58c8765 100644 --- a/.github/workflows/check-bootstrap-testnet-full-sync.yml +++ b/.github/workflows/check-bootstrap-testnet-full-sync.yml @@ -4,9 +4,6 @@ on: # TODO(marun) Add a schedule workflow_dispatch: - # TODO(marun) For testing only - remove before merge - pull_request: - jobs: check_bootstrap_testnet_full_sync: name: Check Bootstrap (testnet,full-sync) diff --git a/.github/workflows/publish_docker_image.yml b/.github/workflows/publish_docker_image.yml index 2674c429bfd..aa0f5085384 100644 --- a/.github/workflows/publish_docker_image.yml +++ b/.github/workflows/publish_docker_image.yml @@ -27,3 +27,9 @@ jobs: DOCKER_PASS: ${{ secrets.docker_pass }} DOCKER_IMAGE: ${{ secrets.docker_repo }} run: scripts/build_image.sh + - name: Build and publish boostrap tester image to DockerHub + env: + DOCKER_USERNAME: ${{ secrets.docker_username }} + DOCKER_PASS: ${{ secrets.docker_pass }} + IMAGE_PREFIX: avaplatform + run: bash -x scripts/build_bootstrap_tester_image.sh diff --git a/scripts/build_bootstrap_tester.sh b/scripts/build_bootstrap_tester.sh new file mode 100755 index 00000000000..6978fc1eaa5 --- /dev/null +++ b/scripts/build_bootstrap_tester.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if ! [[ "$0" =~ scripts/build_bootstrap_tester.sh ]]; then + echo "must be run from repository root" + exit 255 +fi + +source ./scripts/constants.sh + +echo "Building bootstrap tester..." +go build -o ./build/bootstrap-tester ./tests/bootstrap/ diff --git a/scripts/build_bootstrap_tester_image.sh b/scripts/build_bootstrap_tester_image.sh new file mode 100755 index 00000000000..df135e5be59 --- /dev/null +++ b/scripts/build_bootstrap_tester_image.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Directory above this script +AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) + +source ./scripts/constants.sh + +IMAGE_NAME="bootstrap-tester" + +IMAGE_TAG="${IMAGE_TAG:-}" +if [[ -z "${IMAGE_TAG}" ]]; then + # Default to tagging with the commit hash + IMAGE_TAG="${commit_hash}" +fi + +# Build the avalanchego image +DOCKER_CMD="docker buildx build" + +# Specifying an image prefix will ensure the image is pushed after build +IMAGE_PREFIX="${IMAGE_PREFIX:-}" +if [[ -n "${IMAGE_PREFIX}" ]]; then + IMAGE_NAME="${IMAGE_PREFIX}/${IMAGE_NAME}" + DOCKER_CMD="${DOCKER_CMD} --push" + + # Tag the image as latest for the master branch + if [[ "${image_tag}" == "master" ]]; then + DOCKER_CMD="${DOCKER_CMD} -t ${IMAGE_NAME}:latest" + fi + + # A populated DOCKER_USERNAME env var triggers login + if [[ -n "${DOCKER_USERNAME:-}" ]]; then + echo "$DOCKER_PASS" | docker login --username "$DOCKER_USERNAME" --password-stdin + fi + + # The avalanchego image will have already have been built + AVALANCHEGO_NODE_IMAGE="${IMAGE_PREFIX}/avalanchego:${IMAGE_TAG}" +else + # Build the avalanchego image locally + ./scripts/build_image.sh + AVALANCHEGO_NODE_IMAGE="avalanchego:${IMAGE_TAG}" +fi + +# The dockerfiles don't specify the golang version to minimize the changes required to bump +# the version. Instead, the golang version is provided as an argument. +GO_VERSION="$(go list -m -f '{{.GoVersion}}')" + +# Build the image for the bootstrap tester +${DOCKER_CMD} -t "${IMAGE_NAME}:${IMAGE_TAG}" \ + --build-arg GO_VERSION="${GO_VERSION}" --build-arg AVALANCHEGO_NODE_IMAGE="${AVALANCHEGO_NODE_IMAGE}" \ + -f "${AVALANCHE_PATH}/tests/bootstrap/Dockerfile" "${AVALANCHE_PATH}" diff --git a/tests/bootstrap/Dockerfile b/tests/bootstrap/Dockerfile new file mode 100644 index 00000000000..02906a3cad7 --- /dev/null +++ b/tests/bootstrap/Dockerfile @@ -0,0 +1,34 @@ +# The version is supplied as a build argument rather than hard-coded +# to minimize the cost of version changes. +ARG GO_VERSION + +# AVALANCHEGO_NODE_IMAGE needs to identify an existing node image and should include the tag +ARG AVALANCHEGO_NODE_IMAGE + +FROM golang:$GO_VERSION-bullseye AS builder + +WORKDIR /builder_workdir + +# Copy and download avalanche dependencies using go mod +COPY go.mod . +COPY go.sum . +RUN go mod download + +# Copy the code into the container +COPY . . + +# Ensure pre-existing builds are not available for inclusion in the final image +RUN [ -d ./build ] && rm -rf ./build/* || true + +# Build tester binary +RUN ./scripts/build_bootstrap_tester.sh + +# ============= Cleanup Stage ================ +FROM $AVALANCHEGO_NODE_IMAGE AS execution + +COPY --from=builder /builder_workdir/build/bootstrap-tester /avalanchego/build/bootstrap-tester + +# Clear the CMD set by the base image +CMD [ "" ] + +ENTRYPOINT [ "/avalanchego/build/bootstrap-tester" ] diff --git a/tests/bootstrap/main.go b/tests/bootstrap/main.go index c6dee4f6c4b..cff7338759d 100644 --- a/tests/bootstrap/main.go +++ b/tests/bootstrap/main.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/logging" ) @@ -25,6 +26,8 @@ func main() { networkID := flag.Int64("network-id", 0, "The ID of the network to bootstrap from") stateSyncEnabled := flag.Bool("state-sync-enabled", false, "Whether state syncing should be enabled") maxDuration := flag.Duration("max-duration", time.Hour*72, "The maximum duration the network should run for") + dataDir := flag.String("data-dir", "", "The directory to store the node's data") + useDynamicPorts := flag.Bool("use-dynamic-ports", false, "Whether the bootstrapping node should bind to dynamic ports") flag.Parse() @@ -38,25 +41,43 @@ func main() { log.Fatal("max-duration is required") } - if err := checkBootstrap(*avalanchegoPath, uint32(*networkID), *stateSyncEnabled, *maxDuration); err != nil { + if err := checkBootstrap(*dataDir, *avalanchegoPath, uint32(*networkID), *useDynamicPorts, *stateSyncEnabled, *maxDuration); err != nil { log.Fatalf("Failed to check bootstrap: %v\n", err) } } -func checkBootstrap(avalanchegoPath string, networkID uint32, stateSyncEnabled bool, maxDuration time.Duration) error { +func checkBootstrap( + dataDir string, + avalanchegoPath string, + networkID uint32, + useDynamicPorts bool, + stateSyncEnabled bool, + maxDuration time.Duration, +) error { flags := tmpnet.DefaultLocalhostFlags() flags.SetDefaults(tmpnet.FlagsMap{ config.HealthCheckFreqKey: "30s", // Minimize logging overhead config.LogDisplayLevelKey: logging.Off.String(), - config.LogLevelKey: logging.Info.String(), }) + if !useDynamicPorts { + flags.SetDefaults(tmpnet.FlagsMap{ + config.HTTPPortKey: config.DefaultHTTPPort, + config.StakingPortKey: config.DefaultStakingPort, + }) + } + + networkName := constants.NetworkName(networkID) + syncString := "full-sync" + if stateSyncEnabled { + syncString = "state-sync" + } // Create a new single-node network that will bootstrap from the specified network network := &tmpnet.Network{ UUID: uuid.NewString(), NetworkID: networkID, - Owner: "bootstrap-test", + Owner: fmt.Sprintf("bootstrap-test-%s-%s", networkName, syncString), Nodes: tmpnet.NewNodesOrPanic(1), DefaultFlags: flags, DefaultRuntimeConfig: tmpnet.NodeRuntimeConfig{ @@ -70,7 +91,7 @@ func checkBootstrap(avalanchegoPath string, networkID uint32, stateSyncEnabled b }, } - if err := network.Create(""); err != nil { + if err := network.Create(dataDir); err != nil { return fmt.Errorf("failed to create network: %w", err) } node := network.Nodes[0] From a66716234d33a687764cfd17bf4a46b219bd1433 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Wed, 24 Jul 2024 16:29:27 -0700 Subject: [PATCH 07/11] fixup: optionally wipe the data dir to better support PVC usage --- tests/bootstrap/main.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/bootstrap/main.go b/tests/bootstrap/main.go index cff7338759d..539882c686c 100644 --- a/tests/bootstrap/main.go +++ b/tests/bootstrap/main.go @@ -28,6 +28,7 @@ func main() { maxDuration := flag.Duration("max-duration", time.Hour*72, "The maximum duration the network should run for") dataDir := flag.String("data-dir", "", "The directory to store the node's data") useDynamicPorts := flag.Bool("use-dynamic-ports", false, "Whether the bootstrapping node should bind to dynamic ports") + wipeDataDir := flag.Bool("wipe-data-dir", false, "Whether to wipe the data dir in preparation for testing") flag.Parse() @@ -40,8 +41,11 @@ func main() { if *maxDuration == 0 { log.Fatal("max-duration is required") } + if len(*dataDir) == 0 && *wipeDataDir { + log.Fatal("unable to wipe the data dir when a path is not provided") + } - if err := checkBootstrap(*dataDir, *avalanchegoPath, uint32(*networkID), *useDynamicPorts, *stateSyncEnabled, *maxDuration); err != nil { + if err := checkBootstrap(*dataDir, *avalanchegoPath, uint32(*networkID), *useDynamicPorts, *wipeDataDir, *stateSyncEnabled, *maxDuration); err != nil { log.Fatalf("Failed to check bootstrap: %v\n", err) } } @@ -51,6 +55,7 @@ func checkBootstrap( avalanchegoPath string, networkID uint32, useDynamicPorts bool, + wipeDataDir bool, stateSyncEnabled bool, maxDuration time.Duration, ) error { @@ -67,6 +72,12 @@ func checkBootstrap( }) } + if wipeDataDir && len(dataDir) > 0 { + if err := os.RemoveAll(dataDir); err != nil { + return fmt.Errorf("failed to remove data dir: %w", err) + } + } + networkName := constants.NetworkName(networkID) syncString := "full-sync" if stateSyncEnabled { From 7c2f0f2c671ba809a0d65bb787d7e9c6da84b810 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Wed, 24 Jul 2024 18:30:45 -0700 Subject: [PATCH 08/11] fixup: Add a check for disk usage --- tests/bootstrap/main.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/bootstrap/main.go b/tests/bootstrap/main.go index 539882c686c..fd39c49b181 100644 --- a/tests/bootstrap/main.go +++ b/tests/bootstrap/main.go @@ -9,6 +9,8 @@ import ( "fmt" "log" "os" + "os/exec" + "strings" "time" "github.com/google/uuid" @@ -137,5 +139,16 @@ func checkBootstrap( log.Print("Bootstrap completed successfully!\n") + // Check disk usage + if len(dataDir) == 0 { + dataDir = os.ExpandEnv("$HOME/.tmpnet/networks") + } + cmd := exec.Command("du", "-sh", dataDir) + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("failed to check disk usage: %v", err) + } + log.Printf("Disk usage: %s\n", strings.TrimSpace(string(output))) + return nil } From e68d9618a76fbe33333970d6355e01f924dd7869 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Wed, 24 Jul 2024 20:18:56 -0700 Subject: [PATCH 09/11] fixup: Add k8s configuration for manually-invoked bootstrap testing --- tests/bootstrap/mainnet-full-sync-pod.yml | 31 ++++++++++++++++++++++ tests/bootstrap/mainnet-full-sync-pvc.yml | 11 ++++++++ tests/bootstrap/mainnet-state-sync-pod.yml | 31 ++++++++++++++++++++++ tests/bootstrap/mainnet-state-sync-pvc.yml | 11 ++++++++ tests/bootstrap/testnet-full-sync-pod.yml | 31 ++++++++++++++++++++++ tests/bootstrap/testnet-full-sync-pvc.yml | 11 ++++++++ tests/bootstrap/testnet-state-sync-pod.yml | 31 ++++++++++++++++++++++ tests/bootstrap/testnet-state-sync-pvc.yml | 11 ++++++++ 8 files changed, 168 insertions(+) create mode 100644 tests/bootstrap/mainnet-full-sync-pod.yml create mode 100644 tests/bootstrap/mainnet-full-sync-pvc.yml create mode 100644 tests/bootstrap/mainnet-state-sync-pod.yml create mode 100644 tests/bootstrap/mainnet-state-sync-pvc.yml create mode 100644 tests/bootstrap/testnet-full-sync-pod.yml create mode 100644 tests/bootstrap/testnet-full-sync-pvc.yml create mode 100644 tests/bootstrap/testnet-state-sync-pod.yml create mode 100644 tests/bootstrap/testnet-state-sync-pvc.yml diff --git a/tests/bootstrap/mainnet-full-sync-pod.yml b/tests/bootstrap/mainnet-full-sync-pod.yml new file mode 100644 index 00000000000..0a31ca76c83 --- /dev/null +++ b/tests/bootstrap/mainnet-full-sync-pod.yml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Pod +metadata: + generateName: bootstrap-tester-mainnet-full-sync- +spec: + containers: + - name: bootstrap-tester + image: marunava/bootstrap-tester:0a63c561 + args: + - "--avalanchego-path=/avalanchego/build/avalanchego" + - "--network-id=1" + - "--state-sync-enabled=false" + - "--max-duration=480h" + - "--data-dir=/data" + ports: + - containerPort: 9650 + - containerPort: 9651 + volumeMounts: + - name: data + mountPath: /data + resources: + limits: + cpu: "8" + memory: 16Gi + requests: + cpu: "1" + memory: 2Gi + volumes: + - name: data + persistentVolumeClaim: + claimName: bootstrap-tester-mainnet-full-sync diff --git a/tests/bootstrap/mainnet-full-sync-pvc.yml b/tests/bootstrap/mainnet-full-sync-pvc.yml new file mode 100644 index 00000000000..f0912272407 --- /dev/null +++ b/tests/bootstrap/mainnet-full-sync-pvc.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: bootstrap-tester-mainnet-full-sync +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2000Gi + storageClassName: ebs-fast diff --git a/tests/bootstrap/mainnet-state-sync-pod.yml b/tests/bootstrap/mainnet-state-sync-pod.yml new file mode 100644 index 00000000000..26009ba7b83 --- /dev/null +++ b/tests/bootstrap/mainnet-state-sync-pod.yml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Pod +metadata: + generateName: bootstrap-tester-mainnet-state-sync- +spec: + containers: + - name: bootstrap-tester + image: marunava/bootstrap-tester:0a63c561 + args: + - "--avalanchego-path=/avalanchego/build/avalanchego" + - "--network-id=1" + - "--state-sync-enabled=true" + - "--max-duration=240h" + - "--data-dir=/data" + ports: + - containerPort: 9650 + - containerPort: 9651 + volumeMounts: + - name: data + mountPath: /data + resources: + limits: + cpu: "8" + memory: 16Gi + requests: + cpu: "1" + memory: 2Gi + volumes: + - name: data + persistentVolumeClaim: + claimName: bootstrap-tester-mainnet-state-sync diff --git a/tests/bootstrap/mainnet-state-sync-pvc.yml b/tests/bootstrap/mainnet-state-sync-pvc.yml new file mode 100644 index 00000000000..138add9e3e7 --- /dev/null +++ b/tests/bootstrap/mainnet-state-sync-pvc.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: bootstrap-tester-mainnet-state-sync +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2000Gi + storageClassName: ebs-fast diff --git a/tests/bootstrap/testnet-full-sync-pod.yml b/tests/bootstrap/testnet-full-sync-pod.yml new file mode 100644 index 00000000000..059bbead54f --- /dev/null +++ b/tests/bootstrap/testnet-full-sync-pod.yml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Pod +metadata: + generateName: bootstrap-tester-testnet-full-sync- +spec: + containers: + - name: bootstrap-tester + image: marunava/bootstrap-tester:0a63c561 + args: + - "--avalanchego-path=/avalanchego/build/avalanchego" + - "--network-id=5" + - "--state-sync-enabled=false" + - "--max-duration=240h" + - "--data-dir=/data" + ports: + - containerPort: 9650 + - containerPort: 9651 + volumeMounts: + - name: data + mountPath: /data + resources: + limits: + cpu: "8" + memory: 16Gi + requests: + cpu: "1" + memory: 2Gi + volumes: + - name: data + persistentVolumeClaim: + claimName: bootstrap-tester-testnet-full-sync diff --git a/tests/bootstrap/testnet-full-sync-pvc.yml b/tests/bootstrap/testnet-full-sync-pvc.yml new file mode 100644 index 00000000000..6ed3fb853a6 --- /dev/null +++ b/tests/bootstrap/testnet-full-sync-pvc.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: bootstrap-tester-testnet-full-sync +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1000Gi + storageClassName: ebs-fast diff --git a/tests/bootstrap/testnet-state-sync-pod.yml b/tests/bootstrap/testnet-state-sync-pod.yml new file mode 100644 index 00000000000..2f9dd880048 --- /dev/null +++ b/tests/bootstrap/testnet-state-sync-pod.yml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: Pod +metadata: + generateName: bootstrap-tester-testnet-state-sync- +spec: + containers: + - name: bootstrap-tester + image: marunava/bootstrap-tester:0a63c561 + args: + - "--avalanchego-path=/avalanchego/build/avalanchego" + - "--network-id=5" + - "--state-sync-enabled=true" + - "--max-duration=72h" + - "--data-dir=/data" + ports: + - containerPort: 9650 + - containerPort: 9651 + volumeMounts: + - name: data + mountPath: /data + resources: + limits: + cpu: "8" + memory: 16Gi + requests: + cpu: "1" + memory: 2Gi + volumes: + - name: data + persistentVolumeClaim: + claimName: bootstrap-tester-testnet-state-sync diff --git a/tests/bootstrap/testnet-state-sync-pvc.yml b/tests/bootstrap/testnet-state-sync-pvc.yml new file mode 100644 index 00000000000..96a524d214f --- /dev/null +++ b/tests/bootstrap/testnet-state-sync-pvc.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: bootstrap-tester-testnet-state-sync +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi + storageClassName: ebs-fast From 320478ff5a0206436ab18fcc7587b4b0d97eb36f Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Wed, 24 Jul 2024 20:19:42 -0700 Subject: [PATCH 10/11] fixup: Ensure bootstrap tester image build is multiplatform This supports building an image on a mac that will work in an amd64 eks cluster. --- scripts/build_bootstrap_tester_image.sh | 19 ++++++++++------- scripts/build_image.sh | 5 +++++ tests/bootstrap/Dockerfile | 27 +++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/scripts/build_bootstrap_tester_image.sh b/scripts/build_bootstrap_tester_image.sh index df135e5be59..557be5dd72c 100755 --- a/scripts/build_bootstrap_tester_image.sh +++ b/scripts/build_bootstrap_tester_image.sh @@ -18,10 +18,20 @@ fi # Build the avalanchego image DOCKER_CMD="docker buildx build" +# TODO(marun) Figure out how best to support this in CI and locally +PLATFORMS="${PLATFORMS:-}" + +# Build the avalanchego image local-only +PLATFORMS="${PLATFORMS}" IMAGE_PREFIX= ./scripts/build_image.sh +AVALANCHEGO_NODE_IMAGE="avalanchego:${IMAGE_TAG}" + # Specifying an image prefix will ensure the image is pushed after build IMAGE_PREFIX="${IMAGE_PREFIX:-}" if [[ -n "${IMAGE_PREFIX}" ]]; then IMAGE_NAME="${IMAGE_PREFIX}/${IMAGE_NAME}" + if [[ -n "${PLATFORMS}" ]]; then + DOCKER_CMD="${DOCKER_CMD} --platform=${PLATFORMS}" + fi DOCKER_CMD="${DOCKER_CMD} --push" # Tag the image as latest for the master branch @@ -33,19 +43,14 @@ if [[ -n "${IMAGE_PREFIX}" ]]; then if [[ -n "${DOCKER_USERNAME:-}" ]]; then echo "$DOCKER_PASS" | docker login --username "$DOCKER_USERNAME" --password-stdin fi - - # The avalanchego image will have already have been built - AVALANCHEGO_NODE_IMAGE="${IMAGE_PREFIX}/avalanchego:${IMAGE_TAG}" -else - # Build the avalanchego image locally - ./scripts/build_image.sh - AVALANCHEGO_NODE_IMAGE="avalanchego:${IMAGE_TAG}" fi # The dockerfiles don't specify the golang version to minimize the changes required to bump # the version. Instead, the golang version is provided as an argument. GO_VERSION="$(go list -m -f '{{.GoVersion}}')" +PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}" + # Build the image for the bootstrap tester ${DOCKER_CMD} -t "${IMAGE_NAME}:${IMAGE_TAG}" \ --build-arg GO_VERSION="${GO_VERSION}" --build-arg AVALANCHEGO_NODE_IMAGE="${AVALANCHEGO_NODE_IMAGE}" \ diff --git a/scripts/build_image.sh b/scripts/build_image.sh index d587f41373e..890ecca44d9 100755 --- a/scripts/build_image.sh +++ b/scripts/build_image.sh @@ -71,6 +71,11 @@ if [[ "${DOCKER_IMAGE}" == *"/"* ]]; then echo "$DOCKER_PASS" | docker login --username "$DOCKER_USERNAME" --password-stdin fi else + PLATFORMS="${PLATFORMS:-}" + if [[ -n "${PLATFORMS}" ]]; then + DOCKER_CMD="${DOCKER_CMD} --platform=${PLATFORMS}" + fi + # Build a single-arch image since the image name does not include a slash which # indicates that a registry is not available. # diff --git a/tests/bootstrap/Dockerfile b/tests/bootstrap/Dockerfile index 02906a3cad7..9c4a1581259 100644 --- a/tests/bootstrap/Dockerfile +++ b/tests/bootstrap/Dockerfile @@ -5,10 +5,30 @@ ARG GO_VERSION # AVALANCHEGO_NODE_IMAGE needs to identify an existing node image and should include the tag ARG AVALANCHEGO_NODE_IMAGE -FROM golang:$GO_VERSION-bullseye AS builder +# ============= Compilation Stage ================ +# Always use the native platform to ensure fast builds +FROM --platform=$BUILDPLATFORM golang:$GO_VERSION-bullseye AS builder WORKDIR /builder_workdir +ARG TARGETPLATFORM +ARG BUILDPLATFORM + +# Configure a cross-compiler if the target platform differs from the build platform. +# +# build_env.sh is used to capture the environmental changes required by the build step since RUN +# environment state is not otherwise persistent. +# TODO(marun) Extract a common multi-platform builder from this and the avalanchego Dockerfile. +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ] && [ "$BUILDPLATFORM" != "linux/arm64" ]; then \ + apt-get update && apt-get install -y gcc-aarch64-linux-gnu && \ + echo "export CC=aarch64-linux-gnu-gcc" > ./build_env.sh \ + ; elif [ "$TARGETPLATFORM" = "linux/amd64" ] && [ "$BUILDPLATFORM" != "linux/amd64" ]; then \ + apt-get update && apt-get install -y gcc-x86-64-linux-gnu && \ + echo "export CC=x86_64-linux-gnu-gcc" > ./build_env.sh \ + ; else \ + echo "export CC=gcc" > ./build_env.sh \ + ; fi + # Copy and download avalanche dependencies using go mod COPY go.mod . COPY go.sum . @@ -21,7 +41,10 @@ COPY . . RUN [ -d ./build ] && rm -rf ./build/* || true # Build tester binary -RUN ./scripts/build_bootstrap_tester.sh +RUN . ./build_env.sh && \ + echo "{CC=$CC, TARGETPLATFORM=$TARGETPLATFORM, BUILDPLATFORM=$BUILDPLATFORM}" && \ + export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \ + ./scripts/build_bootstrap_tester.sh # ============= Cleanup Stage ================ FROM $AVALANCHEGO_NODE_IMAGE AS execution From 53230b7ba36d5a491bcf000df038213016dd574a Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 26 Jul 2024 07:09:07 -0700 Subject: [PATCH 11/11] WIP: Add jwt creation --- go.mod | 1 + go.sum | 2 ++ tests/bootstrap/jwt/main.go | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 tests/bootstrap/jwt/main.go diff --git a/go.mod b/go.mod index f847fee1dd8..91956e1b2a2 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/compose-spec/compose-go v1.20.2 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 github.com/ethereum/go-ethereum v1.13.8 + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/btree v1.1.2 github.com/google/renameio/v2 v2.0.0 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 5881b9005f4..c5dcbc1ffa0 100644 --- a/go.sum +++ b/go.sum @@ -240,6 +240,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= diff --git a/tests/bootstrap/jwt/main.go b/tests/bootstrap/jwt/main.go new file mode 100644 index 00000000000..d09c46cda45 --- /dev/null +++ b/tests/bootstrap/jwt/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "time" + + "github.com/golang-jwt/jwt/v4" +) + +func main() { + // Define command-line flags + appID := flag.String("app-id", "", "GitHub App App ID") + privateKeyPath := flag.String("private-key-path", "", "Path to the GitHub App private key file") + expiryDuration := flag.Duration("expiry", 10*time.Minute, "JWT expiration duration (e.g., 10m, 1h)") + + // Parse command-line flags + flag.Parse() + + // Validate required flags + if *appID == "" || *privateKeyPath == "" { + log.Fatalf("Both app-id and private-key-path are required") + } + + // Read the private key file + privateKeyData, err := ioutil.ReadFile(*privateKeyPath) + if err != nil { + log.Fatalf("Error reading private key file: %v", err) + } + + // Parse the private key + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyData) + if err != nil { + log.Fatalf("Error parsing private key: %v", err) + } + + // Create the JWT claims + claims := jwt.MapClaims{ + "iat": time.Now().Unix(), // Issued at time + "exp": time.Now().Add(*expiryDuration).Unix(), // JWT expiration time + "iss": *appID, // GitHub App App ID + } + + // Create the JWT + token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) + jwtToken, err := token.SignedString(privateKey) + if err != nil { + log.Fatalf("Error signing JWT: %v", err) + + } + + // Output the JWT token + fmt.Print(jwtToken) +}