From b548135feaa274a2db14cccac72e6e16d900d1d4 Mon Sep 17 00:00:00 2001 From: Michael McGee Date: Fri, 12 Apr 2024 20:30:49 +0000 Subject: [PATCH] fddev: add CI smoke test --- .github/workflows/on_pull_request.yml | 4 - .github/workflows/tests.yml | 1 + .github/workflows/tests_fddev.yml | 26 --- config/everything.mk | 36 +++- contrib/test/ci_tests.sh | 5 +- contrib/test/run_integration_tests.sh | 211 +++++++++++++++++++- solana | 2 +- src/app/fdctl/fdctl.h | 1 + src/app/fdctl/run/run.c | 11 +- src/app/fdctl/run/run.h | 3 +- src/app/fddev/Local.mk | 27 ++- src/app/fddev/dev.c | 5 +- src/app/fddev/fddev.h | 3 + src/app/fddev/main.c | 182 +---------------- src/app/fddev/main1.c | 185 +++++++++++++++++ src/app/fddev/tests/test_fddev.c | 192 ++++++++++++++++++ src/app/fddev/tests/test_single_transfer.sh | 27 --- src/app/fddev/tests/test_single_txn.sh | 27 --- 18 files changed, 658 insertions(+), 290 deletions(-) delete mode 100644 .github/workflows/tests_fddev.yml create mode 100644 src/app/fddev/main1.c create mode 100644 src/app/fddev/tests/test_fddev.c delete mode 100755 src/app/fddev/tests/test_single_transfer.sh delete mode 100755 src/app/fddev/tests/test_single_txn.sh diff --git a/.github/workflows/on_pull_request.yml b/.github/workflows/on_pull_request.yml index 9b3a3d7206..ef59f69187 100644 --- a/.github/workflows/on_pull_request.yml +++ b/.github/workflows/on_pull_request.yml @@ -9,7 +9,3 @@ jobs: tests: uses: ./.github/workflows/tests.yml secrets: inherit - - tests-fddev: - uses: ./.github/workflows/tests_fddev.yml - secrets: inherit diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 72e947bf76..a2feb73685 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,6 +38,7 @@ jobs: - uses: ./.github/actions/deps - uses: ./.github/actions/hugepages + - uses: dtolnay/rust-toolchain@1.73.0 - name: Run tests run: | diff --git a/.github/workflows/tests_fddev.yml b/.github/workflows/tests_fddev.yml deleted file mode 100644 index f43a63947c..0000000000 --- a/.github/workflows/tests_fddev.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Run integration tests. -# When these tests are stable and no longer require Rust, run them as -# part of the regular unit tests. - -name: Integration Tests -on: - workflow_call: - workflow_dispatch: -jobs: - test_fddev: - runs-on: - group: github-v1 - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install ausearch for debugging seccomp/bad syscalls - run: sudo apt update && sudo apt install -y auditd - - - uses: dtolnay/rust-toolchain@1.73.0 - - - uses: ./.github/actions/deps - - - run: contrib/make-j fddev - - run: make run-integration-test diff --git a/config/everything.mk b/config/everything.mk index e0835f5dc0..3cf8fc02d7 100644 --- a/config/everything.mk +++ b/config/everything.mk @@ -1,8 +1,8 @@ MAKEFLAGS += --no-builtin-rules MAKEFLAGS += --no-builtin-variables .SUFFIXES: -.PHONY: all info bin rust include lib unit-test fuzz-test help clean distclean asm ppp show-deps -.PHONY: run-unit-test run-script-test run-fuzz-test run-integration-test +.PHONY: all info bin rust include lib unit-test integration-test fuzz-test help clean distclean asm ppp show-deps +.PHONY: run-unit-test run-integration-test run-script-test run-fuzz-test .PHONY: seccomp-policies cov-report dist-cov-report .SECONDARY: .SECONDEXPANSION: @@ -13,7 +13,7 @@ CPPFLAGS+=-DFD_BUILD_INFO=\"$(OBJDIR)/info\" CPPFLAGS+=$(EXTRA_CPPFLAGS) # Auxiliary rules that should not set up dependencies -AUX_RULES:=clean distclean help show-deps run-unit-test cov-report dist-cov-report +AUX_RULES:=clean distclean help show-deps run-unit-test run-integration-test cov-report dist-cov-report all: info bin include lib unit-test fuzz-test @@ -45,15 +45,17 @@ help: # SCRUB = $(SCRUB) # FUZZFLAGS = $(FUZZFLAGS) # EXTRAS_CPPFLAGS = $(EXTRA_CPPFLAGS) - # Explicit goals are: all bin include lib unit-test help clean distclean asm ppp + # Explicit goals are: all bin include lib unit-test integration-test help clean distclean asm ppp # "make all" is equivalent to "make bin include lib unit-test fuzz-test" # "make info" makes build info $(OBJDIR)/info for the current platform (if not already made) # "make bin" makes all binaries for the current platform (except those requiring the Rust toolchain) # "make include" makes all include files for the current platform # "make lib" makes all libraries for the current platform # "make unit-test" makes all unit-tests for the current platform + # "make integration-test" makes all integration-tests for the current platform # "make rust" makes all binaries for the current platform that require the Rust toolchain # "make run-unit-test" runs all unit-tests for the current platform. NOTE: this will not (re)build the test executables + # "make run-integration-test" runs all integration-tests for the current platform. NOTE: this will not (re)build the test executables # "make help" prints this message # "make clean" removes editor temp files and the current platform build # "make distclean" removes editor temp files and all platform builds @@ -89,6 +91,12 @@ run-unit-test: ####################################################################### contrib/test/run_unit_tests.sh --tests $(OBJDIR)/unit-test/automatic.txt $(TEST_OPTS) +run-integration-test: + ####################################################################### + # Running integration tests + ####################################################################### + contrib/test/run_integration_tests.sh --tests $(OBJDIR)/integration-test/automatic.txt $(TEST_OPTS) + ############################## # Usage: $(call make-lib,name) @@ -175,7 +183,9 @@ add-test-scripts = $(foreach script,$(1),$(eval $(call _add-script,unit-test,$(s # Usage: $(call make-bin,name,objs,libs) # Usage: $(call make-shared,name,objs,libs) # Usage: $(call make-unit-test,name,objs,libs) +# Usage: $(call make-integration-test,name,objs,libs) # Usage: $(call run-unit-test,name,args) +# Usage: $(call run-integration-test,name,args) # Usage: $(call make-fuzz-test,name,objs,libs) # Note: The library arguments require customization of each target @@ -212,6 +222,16 @@ $(OBJDIR)/unit-test/automatic.txt: $(MKDIR) "$(OBJDIR)/unit-test" @$(foreach test,$(RUN_UNIT_TEST),echo $(test)>>$@;) +# Generate list of automatic integration tests from $(call run-integration-test,...) +integration-test: $(OBJDIR)/integration-test/automatic.txt +define _run-integration-test +RUN_INTEGRATION_TEST+=$(OBJDIR)/integration-test/$(1) +endef +$(OBJDIR)/integration-test/automatic.txt: + $(MKDIR) "$(OBJDIR)/integration-test" + @$(foreach test,$(RUN_INTEGRATION_TEST),echo $(test)>>$@;) + $(TOUCH) "$@" + ifndef FD_HAS_FUZZ FUZZ_EXTRA:=$(OBJDIR)/lib/libfd_fuzz_stub.a endif @@ -247,6 +267,8 @@ make-bin-rust = $(eval $(call _make-exe,$(1),$(2),$(3),rust,bin)) make-shared = $(eval $(call _make-exe,$(1),$(2),$(3),lib,lib,-shared)) make-unit-test = $(eval $(call _make-exe,$(1),$(2),$(3),unit-test,unit-test)) run-unit-test = $(eval $(call _run-unit-test,$(1))) +make-integration-test = $(eval $(call _make-exe,$(1),$(2),$(3),integration-test,integration-test)) +run-integration-test = $(eval $(call _run-integration-test,$(1))) make-fuzz-test = $(eval $(call _fuzz-test,$(1),$(2),$(3))) ############################## @@ -402,12 +424,6 @@ MACHINE=$(MACHINE) \ LLVM_PROFILE_FILE="$(OBJDIR)/cov/raw/script_test-%p.profraw" \ contrib/test/run_script_tests.sh -run-integration-test: fddev - mkdir -p "$(OBJDIR)/cov/raw" && \ -OBJDIR=$(OBJDIR) \ -LLVM_PROFILE_FILE="$(OBJDIR)/cov/raw/integration_tests.profraw" \ -contrib/test/run_integration_tests.sh - seccomp-policies: $(FIND) . -name '*.seccomppolicy' -exec $(PYTHON) contrib/codegen/generate_filters.py {} \; diff --git a/contrib/test/ci_tests.sh b/contrib/test/ci_tests.sh index 9cf5261c5c..44f081fabb 100755 --- a/contrib/test/ci_tests.sh +++ b/contrib/test/ci_tests.sh @@ -29,9 +29,12 @@ for MACHINE in ${MACHINES[*]}; do OBJDIR="$(make help | grep OBJDIR | awk '{print $4}')" OBJDIRS+=( "${OBJDIR}" ) make clean --silent >/dev/null - contrib/make-j + contrib/make-j all integration-test if [[ "$NOTEST" != 1 ]]; then make run-unit-test + if [[ "$EXTRAS" != *"ubsan"* && "$EXTRAS" != *"asan"* ]]; then + make run-integration-test + fi make run-fuzz-test make run-script-test if [[ "$HAS_LLVM_COV" == 1 ]]; then diff --git a/contrib/test/run_integration_tests.sh b/contrib/test/run_integration_tests.sh index d926d2b34a..27c80c1e6d 100755 --- a/contrib/test/run_integration_tests.sh +++ b/contrib/test/run_integration_tests.sh @@ -1,13 +1,212 @@ -#!/bin/bash +#!/usr/bin/env bash +# run_integration_tests.sh is like run_unit_tests.sh except that tests +# are not run concurrently, only one integration test will currently run +# at a time. +# +# WARNING: These tests might change your system configuration. -# WARNING: These tests will destroy your system configuration. +set -eou pipefail -export OBJDIR -export LLVM_PROFILE_FILE +# Defaults -set -xeuo pipefail +TESTS= +VERBOSE=0 -cd "$(dirname "$0")/../.." +# Read command-line args + +while [[ $# -gt 0 ]]; do + + FLAG="$1" + shift 1 + + case "$FLAG" in + "--tests") + TESTS="$1" + shift 1 + ;; + "-v") + VERBOSE=1 + ;; + *) + echo "Unknown flag: $FLAG" >&2 + exit 1 + ;; + esac + +done + +if [[ -z "$TESTS" && -z "${RECURSE_GUARD:-}" ]]; then + # No tests given, so indirect test execution through Make and retry. + # This ensures that we select the correct build dir for the current + # environment ($CC, $MACHINE, etc). + # Make then re-executes this file with the proper parameters. + + export RECURSE_GUARD=1 + exec make run-integration-test TEST_OPTS="$*" +fi + +# Ensure we schedule at most one job at a time +AVAILABLE_JOBS=1 + +# Clean up process tree on exit +trap 'exit' INT QUIT TERM + +# Track list of PIDs of child processes +declare -A PIDS=() +# Remember unit name of each PID +declare -A PID2UNIT=() +# Remember logfile name of each PID +declare -A PID2LOG=() + +# Read in list of automatic integration tests to schedule as jobs +declare -a TEST_LIST=() + +if [[ -s "$TESTS" ]]; then + while read -r line; do + if [[ "$line" =~ ^[[:space:]]*# ]]; then + continue + fi + TEST_LIST+=( "$line" ) + done < <(grep -v '^#' "$TESTS") +fi + +echo "test.sh: Scheduling ${#TEST_LIST[@]} tests in sequence" >&2 + +rc_path () { + echo "/tmp/.pid-$1.rc" +} + +runner () { + local pid="$BASHPID" + local prog="$1" + local log="$2" + local logfull="${log%.log}-full.log" + shift 2 + + # Create coverage dir, in case it's used + local covdir; covdir="$(dirname "$prog")/../cov/raw" + mkdir -p "$covdir" + # Set up coverage file (no-op for non-instrumented binary) + local LLVM_PROFILE_FILE + LLVM_PROFILE_FILE="$covdir/$(basename "$prog").profraw" + + set +e + local elapsed + elapsed="$({ \ + time \ + LLVM_PROFILE_FILE="$LLVM_PROFILE_FILE" \ + "sudo" \ + "$prog" \ + "$@" \ + --log-path "$logfull" \ + --log-level-stderr 3 \ + >/dev/null \ + 2>"$log" \ + ; } \ + 2>&1 >/dev/null \ + | grep real \ + | awk '{print $2}' + )" + local ret="$?" + + local rcpath; rcpath="$(rc_path "$pid")" + { + echo "$ret" + echo "$elapsed" + } > "$rcpath" +} + +# dispatch numa_idx cpu_idx cmdline... +# Fork task +dispatch () { + # Craft command line args + local prog="$1" + local progname="${prog##*/}" + shift 1 + + # Create log dir + local logdir + logdir="$(dirname "$(dirname "$prog")")/log/$progname" + mkdir -p "$logdir" + local log; log="$logdir/$(date -u +%Y%m%d-%H%M%S).log" + + if [[ "$VERBOSE" == 1 ]]; then + echo "test.sh: NUMA $numa_idx: $progname" >&2 + fi + + # Dispatch + runner "$prog" "$log" "$@" & + local pid="$!" + + # Remember PID + PIDS["$pid"]=$pid + PID2UNIT["$pid"]="$progname" + PID2LOG["$pid"]="$log" +} + +# sow +# schedule tasks until max concurrency reached +sow () { + if [[ "${#TEST_LIST[@]}" -eq 0 ]]; then return; fi + if [[ "${AVAILABLE_JOBS}" -eq 0 ]]; then return; fi + + # Found a free CPU! + local test="${TEST_LIST[0]}" + TEST_LIST=( "${TEST_LIST[@]:1}" ) + AVAILABLE_JOBS="$(( AVAILABLE_JOBS - 1 ))" + dispatch "$test" +} + +FAIL_CNT=0 + +# reap +# wait for a job to finish +reap () { + wait -n "${PIDS[@]}" + # Clean up finished jobs + for pid in "${PIDS[@]}"; do + if [[ ! -d "/proc/$pid" ]]; then + # Job finished + local rcfile; rcfile="$(rc_path "$pid")" + local rc + local elapsed + { + IFS= read -r rc + IFS= read -r elapsed + } < "$rcfile" + local unit="${PID2UNIT["$pid"]}" + local log="${PID2LOG["$pid"]}" + local logfull="${log%.log}-full.log" + unset PIDS["$pid"] + unset PID2UNIT["$pid"] + unset PID2LOG["$pid"] + AVAILABLE_JOBS="$(( AVAILABLE_JOBS + 1 ))" + if [[ "$rc" -ne 0 ]]; then + FAIL_CNT="$(( FAIL_CNT + 1 ))" + printf "\033[0;31mFAIL\033[0m%12s %s (exit %d): %s\n" "$elapsed" "$unit" "$rc" "$logfull" >&2 + grep -sv "Log at" "$log" | sed -e "$(printf "s/^/%19s%-20s /" '' "$unit")" || true >&2 + else + printf "\033[0;32mOK \033[0m%12s %s\n" "$elapsed" "$unit" >&2 + fi + fi + done +} + +while [[ "${#TEST_LIST[@]}" -gt 0 ]]; do + sow + reap +done +while [[ "${#PIDS[@]}" -gt 0 ]]; do + reap +done + +if [[ "$FAIL_CNT" -gt 0 ]]; then + echo -e "\033[0;31mFAIL\033[0m ($FAIL_CNT failure)" >&2 + exit 1 +else + echo -e "\033[0;32mPASS\033[0m" >&2 + exit 0 +fi # TODO add fddev integration tests here diff --git a/solana b/solana index 4211f2baaa..650e9d195e 160000 --- a/solana +++ b/solana @@ -1 +1 @@ -Subproject commit 4211f2baaa63b0957276f19856df715fa22e9fea +Subproject commit 650e9d195e3809f85bce3cc2628a26da0bdb0378 diff --git a/src/app/fdctl/fdctl.h b/src/app/fdctl/fdctl.h index c5bc0d18b9..d39139c66d 100644 --- a/src/app/fdctl/fdctl.h +++ b/src/app/fdctl/fdctl.h @@ -37,6 +37,7 @@ typedef union { } configure; struct { + int parent_pipefd; int monitor; int no_configure; int no_solana_labs; diff --git a/src/app/fdctl/run/run.c b/src/app/fdctl/run/run.c index 5a09fdfd34..1110e25e5b 100644 --- a/src/app/fdctl/run/run.c +++ b/src/app/fdctl/run/run.c @@ -385,7 +385,8 @@ clone_firedancer( config_t * const config, died, and it can terminate itself, which due to (a) and (b) will kill all other processes. */ void -run_firedancer( config_t * const config ) { +run_firedancer( config_t * const config, + int parent_pipefd ) { /* dump the topology we are using to the output log */ fd_topo_print_log( 0, &config->topo ); @@ -393,7 +394,7 @@ run_firedancer( config_t * const config ) { if( FD_UNLIKELY( close( 1 ) ) ) FD_LOG_ERR(( "close(1) failed (%i-%s)", errno, fd_io_strerror( errno ) )); int pipefd; - pid_namespace = clone_firedancer( config, -1, &pipefd ); + pid_namespace = clone_firedancer( config, parent_pipefd, &pipefd ); /* Print the location of the logfile on SIGINT or SIGTERM, and also kill the child. They are connected by a pipe which the child is @@ -407,12 +408,14 @@ run_firedancer( config_t * const config ) { struct sock_filter seccomp_filter[ 128UL ]; populate_sock_filter_policy_main( 128UL, seccomp_filter, (uint)fd_log_private_logfile_fd(), (uint)pid_namespace ); - int allow_fds[3]; + int allow_fds[ 4 ]; ulong allow_fds_cnt = 0; allow_fds[ allow_fds_cnt++ ] = 2; /* stderr */ if( FD_LIKELY( fd_log_private_logfile_fd()!=-1 ) ) allow_fds[ allow_fds_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */ allow_fds[ allow_fds_cnt++ ] = pipefd; /* read end of main pipe */ + if( FD_UNLIKELY( parent_pipefd!=-1 ) ) + allow_fds[ allow_fds_cnt++ ] = parent_pipefd; /* write end of parent pipe */ fd_sandbox( config->development.sandbox, config->uid, @@ -451,5 +454,5 @@ run_cmd_fn( args_t * args, "empty. Please remove the empty entrypoint or set it correctly. ")); } - run_firedancer( config ); + run_firedancer( config, -1 ); } diff --git a/src/app/fdctl/run/run.h b/src/app/fdctl/run/run.h index 00fe35ca70..4af51ad2be 100644 --- a/src/app/fdctl/run/run.h +++ b/src/app/fdctl/run/run.h @@ -18,6 +18,7 @@ clone_firedancer( config_t * const config, int * out_pipe ); void -run_firedancer( config_t * const config ); +run_firedancer( config_t * const config, + int parent_pipefd ); #endif /* HEADER_fd_src_app_fdctl_run_h */ diff --git a/src/app/fddev/Local.mk b/src/app/fddev/Local.mk index 2b0c11aaa9..9fe7b65cac 100644 --- a/src/app/fddev/Local.mk +++ b/src/app/fddev/Local.mk @@ -1,11 +1,30 @@ ifdef FD_HAS_HOSTED ifdef FD_HAS_ALLOCA ifdef FD_HAS_DOUBLE +ifdef FD_HAS_INT128 include src/app/fdctl/with-version.mk .PHONY: fddev run monitor $(OBJDIR)/lib/libsolana_genesis.a -$(call make-bin-rust,fddev,main dev dev1 txn bench dump flame tiles/fd_bencho tiles/fd_benchg tiles/fd_benchs configure/netns configure/keys configure/kill configure/genesis,fd_fdctl fd_fddev fd_disco fd_flamenco fd_quic fd_tls fd_reedsol fd_ballet fd_waltz fd_tango fd_util solana_validator solana_genesis) +$(call make-bin-rust,fddev,main main1 dev dev1 txn bench dump flame tiles/fd_bencho tiles/fd_benchg tiles/fd_benchs configure/netns configure/keys configure/kill configure/genesis,fd_fdctl fd_fddev fd_disco fd_flamenco fd_quic fd_tls fd_reedsol fd_ballet fd_waltz fd_tango fd_util solana_validator solana_genesis) + +# TODO: There's a linker error due to the #[global_allocator] when including these files from a library +# so for now they are redeclared. +# fddev core +# $(call add-objs,main1 dev dev1 txn bench dump flame,fd_fddev) + +# fddev tiles +# $(call add-objs,tiles/fd_bencho,fd_fddev) +# $(call add-objs,tiles/fd_benchg,fd_fddev) +# $(call add-objs,tiles/fd_benchs,fd_fddev) + +# fddev configure stages +# $(call add-objs,configure/netns,fd_fddev) +# $(call add-objs,configure/keys,fd_fddev) +# $(call add-objs,configure/kill,fd_fddev) +# $(call add-objs,configure/genesis,fd_fddev) + +# $(call make-bin-rust,fddev,main,fd_fdctl fd_fddev fd_disco fd_flamenco fd_quic fd_tls fd_reedsol fd_ballet fd_waltz fd_tango fd_util solana_validator solana_genesis) solana/target/$(RUST_PROFILE)/libsolana_genesis.a: cargo @@ -36,6 +55,12 @@ endif monitor: bin $(OBJDIR)/bin/fddev monitor $(MONITOR_ARGS) +# TODO: There's a linker error due to the #[global_allocator] when including these files from a library +# so for now they are redeclared. +$(call make-integration-test,test_fddev,tests/test_fddev main1 dev dev1 txn bench dump flame tiles/fd_bencho tiles/fd_benchg tiles/fd_benchs configure/netns configure/keys configure/kill configure/genesis,fd_fdctl fd_fddev fd_disco fd_flamenco fd_quic fd_tls fd_reedsol fd_ballet fd_waltz fd_tango fd_util solana_validator solana_genesis) +$(call run-integration-test,test_fddev) + +endif endif endif endif diff --git a/src/app/fddev/dev.c b/src/app/fddev/dev.c index 5244418c4d..9401edab62 100644 --- a/src/app/fddev/dev.c +++ b/src/app/fddev/dev.c @@ -19,6 +19,7 @@ void dev_cmd_args( int * pargc, char *** pargv, args_t * args ) { + args->dev.parent_pipefd = -1; args->dev.monitor = fd_env_strip_cmdline_contains( pargc, pargv, "--monitor" ); args->dev.no_configure = fd_env_strip_cmdline_contains( pargc, pargv, "--no-configure" ); args->dev.no_solana_labs = fd_env_strip_cmdline_contains( pargc, pargv, "--no-solana-labs" ) || @@ -228,7 +229,7 @@ dev_cmd_fn( args_t * args, } if( FD_LIKELY( !args->dev.monitor ) ) { - if( FD_LIKELY( !config->development.no_clone ) ) run_firedancer( config ); + if( FD_LIKELY( !config->development.no_clone ) ) run_firedancer( config, args->dev.parent_pipefd ); else run_firedancer_threaded( config ); } else { install_parent_signals(); @@ -244,7 +245,7 @@ dev_cmd_fn( args_t * args, if( FD_UNLIKELY( close( pipefd[1] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) )); if( FD_UNLIKELY( setenv( "RUST_LOG_STYLE", "always", 1 ) ) ) /* otherwise RUST_LOG will not be colorized to the pipe */ FD_LOG_ERR(( "setenv() failed (%i-%s)", errno, fd_io_strerror( errno ) )); - if( FD_LIKELY( !config->development.no_clone ) ) run_firedancer( config ); + if( FD_LIKELY( !config->development.no_clone ) ) run_firedancer( config, -1 ); else run_firedancer_threaded( config ); } else { if( FD_UNLIKELY( close( pipefd[1] ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) )); diff --git a/src/app/fddev/fddev.h b/src/app/fddev/fddev.h index 1153520b22..9e20add7f0 100644 --- a/src/app/fddev/fddev.h +++ b/src/app/fddev/fddev.h @@ -3,6 +3,9 @@ #include "../fdctl/fdctl.h" +int fddev_main( int argc, + char ** argv ); + void update_config_for_dev( config_t * const config ); diff --git a/src/app/fddev/main.c b/src/app/fddev/main.c index 8b47cad061..814d38c777 100644 --- a/src/app/fddev/main.c +++ b/src/app/fddev/main.c @@ -1,185 +1,7 @@ -#define _GNU_SOURCE #include "fddev.h" -#include "../fdctl/configure/configure.h" - -#include -#include -#include -#include -#include - -extern configure_stage_t _kill; -extern configure_stage_t netns; -extern configure_stage_t genesis; -extern configure_stage_t keys; - -configure_stage_t * STAGES[ CONFIGURE_STAGE_COUNT ] = { - &_kill, - &netns, - &hugetlbfs, - &sysctl, - &xdp, - &xdp_leftover, - ðtool, - &keys, - &workspace, - &genesis, - NULL, -}; - -static action_t DEV_ACTIONS[] = { - { .name = "dev", .args = dev_cmd_args, .fn = dev_cmd_fn, .perm = dev_cmd_perm }, - { .name = "dev1", .args = dev1_cmd_args, .fn = dev1_cmd_fn, .perm = dev_cmd_perm }, - { .name = "txn", .args = txn_cmd_args, .fn = txn_cmd_fn, .perm = txn_cmd_perm }, - { .name = "bench", .args = bench_cmd_args, .fn = bench_cmd_fn, .perm = bench_cmd_perm }, - { .name = "dump", .args = dump_cmd_args, .fn = dump_cmd_fn, .perm = NULL }, - { .name = "flame", .args = flame_cmd_args, .fn = flame_cmd_fn, .perm = flame_cmd_perm }, -}; - -extern fd_topo_run_tile_t fd_tile_net; -extern fd_topo_run_tile_t fd_tile_netmux; -extern fd_topo_run_tile_t fd_tile_quic; -extern fd_topo_run_tile_t fd_tile_verify; -extern fd_topo_run_tile_t fd_tile_dedup; -extern fd_topo_run_tile_t fd_tile_pack; -extern fd_topo_run_tile_t fd_tile_bank; -extern fd_topo_run_tile_t fd_tile_poh; -extern fd_topo_run_tile_t fd_tile_shred; -extern fd_topo_run_tile_t fd_tile_store; -extern fd_topo_run_tile_t fd_tile_sign; -extern fd_topo_run_tile_t fd_tile_metric; - -extern fd_topo_run_tile_t fd_tile_bencho; -extern fd_topo_run_tile_t fd_tile_benchg; -extern fd_topo_run_tile_t fd_tile_benchs; - -fd_topo_run_tile_t * TILES[] = { - &fd_tile_net, - &fd_tile_netmux, - &fd_tile_quic, - &fd_tile_verify, - &fd_tile_dedup, - &fd_tile_pack, - &fd_tile_bank, - &fd_tile_poh, - &fd_tile_shred, - &fd_tile_store, - &fd_tile_sign, - &fd_tile_metric, - &fd_tile_bencho, - &fd_tile_benchg, - &fd_tile_benchs, - NULL, -}; - -#define MAX_ARGC 32 - -extern char fd_log_private_path[ 1024 ]; - -/* Rerun the currently executing process as root. This will never return, - instead it replaces the currently executing process with a new one. */ -static void -execve_as_root( int argc, - char ** argv ) { - char _current_executable_path[ PATH_MAX ]; - current_executable_path( _current_executable_path ); - - char * args[ MAX_ARGC+4 ]; - for( int i=1; i= MAX_ARGC ) ) FD_LOG_ERR(( "too many arguments (%i)", argc )); - char ** argv = _argv; - - argc--; argv++; - - char const * log_path = fd_env_strip_cmdline_cstr( &argc, &argv, "--log-path", NULL, NULL ); - - fdctl_boot( &argc, &argv, &config, log_path ); - - /* load configuration and command line parsing */ - if( FD_UNLIKELY( config.is_live_cluster ) ) - FD_LOG_ERR(( "The `fddev` command is for development and test environments but your " - "configuration targets a live cluster. Use `fdctl` if this is a " - "production environment" )); - - int no_sandbox = fd_env_strip_cmdline_contains( &argc, &argv, "--no-sandbox" ); - int no_clone = fd_env_strip_cmdline_contains( &argc, &argv, "--no-clone" ); - config.development.no_clone = config.development.no_clone || no_clone; - config.development.sandbox = config.development.sandbox && !no_sandbox && !no_clone; - - const char * action_name = "dev"; - if( FD_UNLIKELY( argc > 0 && argv[ 0 ][ 0 ] != '-' ) ) { - action_name = argv[ 0 ]; - argc--; argv++; - } - - action_t * action = NULL; - for( ulong i=0; iargs ) ) action->args( &argc, &argv, &args ); - if( FD_UNLIKELY( argc ) ) FD_LOG_ERR(( "unknown argument `%s`", argv[ 0 ] )); - - /* check if we are appropriate permissioned to run the desired command */ - if( FD_LIKELY( action->perm ) ) { - fd_caps_ctx_t caps = {0}; - action->perm( &args, &caps, &config ); - if( FD_UNLIKELY( caps.err_cnt ) ) { - execve_as_root( orig_argc, orig_argv ); - } - } - - /* run the command */ - action->fn( &args, &config ); - - return 0; + char ** argv ) { + return fddev_main( argc, argv ); } diff --git a/src/app/fddev/main1.c b/src/app/fddev/main1.c new file mode 100644 index 0000000000..169fad5878 --- /dev/null +++ b/src/app/fddev/main1.c @@ -0,0 +1,185 @@ +#define _GNU_SOURCE +#include "fddev.h" + +#include "../fdctl/configure/configure.h" + +#include +#include +#include +#include +#include + +extern configure_stage_t _kill; +extern configure_stage_t netns; +extern configure_stage_t genesis; +extern configure_stage_t keys; + +configure_stage_t * STAGES[ CONFIGURE_STAGE_COUNT ] = { + &_kill, + &netns, + &hugetlbfs, + &sysctl, + &xdp, + &xdp_leftover, + ðtool, + &keys, + &workspace, + &genesis, + NULL, +}; + +extern fd_topo_run_tile_t fd_tile_net; +extern fd_topo_run_tile_t fd_tile_netmux; +extern fd_topo_run_tile_t fd_tile_quic; +extern fd_topo_run_tile_t fd_tile_verify; +extern fd_topo_run_tile_t fd_tile_dedup; +extern fd_topo_run_tile_t fd_tile_pack; +extern fd_topo_run_tile_t fd_tile_bank; +extern fd_topo_run_tile_t fd_tile_poh; +extern fd_topo_run_tile_t fd_tile_shred; +extern fd_topo_run_tile_t fd_tile_store; +extern fd_topo_run_tile_t fd_tile_sign; +extern fd_topo_run_tile_t fd_tile_metric; + +extern fd_topo_run_tile_t fd_tile_bencho; +extern fd_topo_run_tile_t fd_tile_benchg; +extern fd_topo_run_tile_t fd_tile_benchs; + +fd_topo_run_tile_t * TILES[] = { + &fd_tile_net, + &fd_tile_netmux, + &fd_tile_quic, + &fd_tile_verify, + &fd_tile_dedup, + &fd_tile_pack, + &fd_tile_bank, + &fd_tile_poh, + &fd_tile_shred, + &fd_tile_store, + &fd_tile_sign, + &fd_tile_metric, + &fd_tile_bencho, + &fd_tile_benchg, + &fd_tile_benchs, + NULL, +}; + +static action_t DEV_ACTIONS[] = { + { .name = "dev", .args = dev_cmd_args, .fn = dev_cmd_fn, .perm = dev_cmd_perm }, + { .name = "dev1", .args = dev1_cmd_args, .fn = dev1_cmd_fn, .perm = dev_cmd_perm }, + { .name = "txn", .args = txn_cmd_args, .fn = txn_cmd_fn, .perm = txn_cmd_perm }, + { .name = "bench", .args = bench_cmd_args, .fn = bench_cmd_fn, .perm = bench_cmd_perm }, + { .name = "dump", .args = dump_cmd_args, .fn = dump_cmd_fn, .perm = NULL }, + { .name = "flame", .args = flame_cmd_args, .fn = flame_cmd_fn, .perm = flame_cmd_perm }, +}; + +extern char fd_log_private_path[ 1024 ]; + +#define MAX_ARGC 32 + +/* Rerun the currently executing process as root. This will never return, + instead it replaces the currently executing process with a new one. */ +static void +execve_as_root( int argc, + char ** argv ) { + char _current_executable_path[ PATH_MAX ]; + current_executable_path( _current_executable_path ); + + char * args[ MAX_ARGC+4 ]; + for( int i=1; i= MAX_ARGC ) ) FD_LOG_ERR(( "too many arguments (%i)", argc )); + char ** argv = _argv; + + argc--; argv++; + + fd_env_strip_cmdline_cstr( &argc, &argv, "--log-level-stderr", NULL, NULL ); + char const * log_path = fd_env_strip_cmdline_cstr( &argc, &argv, "--log-path", NULL, NULL ); + + fdctl_boot( &argc, &argv, &config, log_path ); + + /* load configuration and command line parsing */ + if( FD_UNLIKELY( config.is_live_cluster ) ) + FD_LOG_ERR(( "The `fddev` command is for development and test environments but your " + "configuration targets a live cluster. Use `fdctl` if this is a " + "production environment" )); + + int no_sandbox = fd_env_strip_cmdline_contains( &argc, &argv, "--no-sandbox" ); + int no_clone = fd_env_strip_cmdline_contains( &argc, &argv, "--no-clone" ); + config.development.no_clone = config.development.no_clone || no_clone; + config.development.sandbox = config.development.sandbox && !no_sandbox && !no_clone; + + const char * action_name = "dev"; + if( FD_UNLIKELY( argc > 0 && argv[ 0 ][ 0 ] != '-' ) ) { + action_name = argv[ 0 ]; + argc--; argv++; + } + + action_t * action = NULL; + for( ulong i=0; iargs ) ) action->args( &argc, &argv, &args ); + if( FD_UNLIKELY( argc ) ) FD_LOG_ERR(( "unknown argument `%s`", argv[ 0 ] )); + + /* check if we are appropriate permissioned to run the desired command */ + if( FD_LIKELY( action->perm ) ) { + fd_caps_ctx_t caps = {0}; + action->perm( &args, &caps, &config ); + if( FD_UNLIKELY( caps.err_cnt ) ) { + execve_as_root( orig_argc, orig_argv ); + } + } + + /* run the command */ + action->fn( &args, &config ); + return 0; +} diff --git a/src/app/fddev/tests/test_fddev.c b/src/app/fddev/tests/test_fddev.c new file mode 100644 index 0000000000..c17bcb0b4d --- /dev/null +++ b/src/app/fddev/tests/test_fddev.c @@ -0,0 +1,192 @@ +#define _GNU_SOURCE +#include "../fddev.h" +#include "../../fdctl/fdctl.h" +#include "../../fdctl/configure/configure.h" + +#include +#include +#include +#include +#include + +struct child_info { + char const * name; + int pipefd; + int pid; +}; + +static int +fddev_configure( config_t * config, + int pipefd ) { + (void)pipefd; + + fd_log_thread_set( "configure" ); + args_t args = { + .configure.command = CONFIGURE_CMD_INIT, + .configure.stages = {0}, + }; + + ulong stage_idx = 0UL; + for( ulong i=0UL; iname ) ) ) continue; + args.configure.stages[ stage_idx++ ] = STAGES[ i ]; + } + fd_caps_ctx_t caps[ 1 ] = {0}; + configure_cmd_perm( &args, caps, config ); + FD_TEST( !caps->err_cnt ); + configure_cmd_fn( &args, config ); + return 0; +} + +static int +fddev_ready( config_t * config, + int pipefd ) { + (void)pipefd; + + fd_log_thread_set( "ready" ); + args_t args = {0}; + ready_cmd_fn( &args, config ); + return 0; +} + +static int +fddev_dev( config_t * config, + int pipefd ) { + fd_log_thread_set( "dev" ); + args_t args = { + .dev.parent_pipefd = pipefd, + .dev.no_configure = 1, + .dev.no_solana_labs = 0, + .dev.monitor = 0, + }; + args.dev.debug_tile[ 0 ] = '\0'; + fd_caps_ctx_t caps[ 1 ] = {0}; + dev_cmd_perm( &args, caps, config ); + FD_TEST( !caps->err_cnt ); + dev_cmd_fn( &args, config ); + return 0; +} + +static struct child_info +fork_child( char const * name, + config_t * config, + int (* child)( config_t * config, int pipefd ) ) { + int pipefd[2] = {0}; + if( FD_UNLIKELY( -1==pipe( pipefd ) ) ) FD_LOG_ERR(( "pipe failed (%i-%s)", errno, fd_io_strerror( errno ) )); + int pid = fork(); + if( FD_UNLIKELY( -1==pid ) ) FD_LOG_ERR(( "fork failed (%i-%s)", errno, fd_io_strerror( errno ) )); + if( !pid ) { + if( FD_UNLIKELY( -1==close( pipefd[ 0 ] ) ) ) FD_LOG_ERR(( "close failed (%i-%s)", errno, fd_io_strerror( errno ) )); + int result = child( config, pipefd[ 1 ] ); + exit_group( result ); + } + if( FD_UNLIKELY( -1==close( pipefd[ 1 ] ) ) ) FD_LOG_ERR(( "close failed (%i-%s)", errno, fd_io_strerror( errno ) )); + return (struct child_info){ .name = name, .pipefd = pipefd[ 0 ], .pid = pid }; +} + +static ulong +wait_children( struct child_info * children, + ulong children_cnt, + ulong timeout_seconds ) { + struct pollfd pfd[ 256 ]; + FD_TEST( children_cnt<=256 ); + for( ulong i=0; ilog.log_fd = fd_log_private_logfile_fd(); + config->log.lock_fd = init_log_memfd(); + config->tick_per_ns_mu = fd_tempo_tick_per_ns( &config->tick_per_ns_sigma ); + + return run( config ); + } else { + int wstatus; + for(;;) { + int exited_pid = waitpid( pid, &wstatus, __WALL ); + if( FD_UNLIKELY( -1==exited_pid && errno==EINTR ) ) continue; + else if( FD_UNLIKELY( -1==exited_pid ) ) FD_LOG_ERR(( "waitpid failed (%i-%s)", errno, fd_io_strerror( errno ) )); + else if( FD_UNLIKELY( !exited_pid ) ) FD_LOG_ERR(( "supervisor did not exit" )); + break; + } + + if( FD_UNLIKELY( !WIFEXITED( wstatus ) ) ) return 128 + WTERMSIG( wstatus ); + else if( FD_UNLIKELY( WEXITSTATUS( wstatus ) ) ) return WEXITSTATUS( wstatus ); + } + } else { + return fddev_main( argc, argv ); + } + + return 0; +} + +static int +test_fddev( config_t * config ) { + struct child_info configure = fork_child( "fddev configure", config, fddev_configure ); + wait_children( &configure, 1UL, 15UL ); + + struct child_info dev = fork_child( "fddev dev", config, fddev_dev ); + struct child_info ready = fork_child( "fddev ready", config, fddev_ready ); + + struct child_info children[ 2 ] = { ready, dev }; + ulong exited = wait_children( children, 2UL, 15UL ); + if( FD_UNLIKELY( exited!=0UL ) ) FD_LOG_ERR(( "`%s` exited unexpectedly", children[ exited ].name )); + return 0; +} + +int +main( int argc, + char ** argv ) { + return fddev_test_run( argc, argv, test_fddev ); +} diff --git a/src/app/fddev/tests/test_single_transfer.sh b/src/app/fddev/tests/test_single_transfer.sh deleted file mode 100755 index b6da5bb604..0000000000 --- a/src/app/fddev/tests/test_single_transfer.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# bash strict mode -set -xeuo pipefail -cd "$(dirname "$0")/../../../.." - -cleanup() { - # TODO: We grep instead of using 'sudo ausearch -c fddev' because ausearch returns 'no matches' - # when it should not. - sudo grep -n fddev /var/log/audit/audit.log -} -trap cleanup EXIT INT TERM - -# start fddev, send a single transfer to the locally running RPC endpoint, and if everything works return 0 -# TODO don't hardcode build path -OBJDIR=${OBJDIR:-build/native/gcc} -FDDEV=${OBJDIR}/bin/fddev - -# TODO: For some reason /tmp does not work on the github runner for --log-path -timeout --preserve-status --kill-after=20 15 $FDDEV configure init all --log-path ./log -timeout --preserve-status --kill-after=20 15 $FDDEV --no-configure --log-path ./log & -FDDEV_PID=$! -sleep 2 -timeout --preserve-status --kill-after=20 15 ./solana/target/release/solana transfer -u http://127.0.0.1:8899 -k /home/$USER/.firedancer/fd1/faucet.json $($FDDEV keys pubkey /home/$USER/.firedancer/fd1/faucet.json) 0.01 -RETVAL=$? -sudo kill $FDDEV_PID -exit $RETVAL diff --git a/src/app/fddev/tests/test_single_txn.sh b/src/app/fddev/tests/test_single_txn.sh deleted file mode 100755 index dcb93a7ac0..0000000000 --- a/src/app/fddev/tests/test_single_txn.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# bash strict mode -set -xeuo pipefail -cd "$(dirname "$0")/../../../.." - -cleanup() { - # TODO: We grep instead of using 'sudo ausearch -c fddev' because ausearch returns 'no matches' - # when it should not. - - sudo grep -n fddev /var/log/audit/audit.log -} -trap cleanup EXIT INT TERM - -# start fddev, send a single transaction, and if everything works return 0 -# TODO don't hardcode build path -OBJDIR=${OBJDIR:-build/native/gcc} -FDDEV=${OBJDIR}/bin/fddev - -# TODO: For some reason /tmp does not work on the github runner for --log-path -timeout --preserve-status --kill-after=20 15 $FDDEV configure init all --netns --log-path ./log -timeout --preserve-status --kill-after=20 15 $FDDEV --no-configure --netns --log-path ./log & -FDDEV_PID=$! -timeout --preserve-status --kill-after=20 15 $FDDEV txn --netns --log-path ./log2 -RETVAL=$? -sudo kill $FDDEV_PID -exit $RETVAL