From e6802690b854a5ddf8a042f5b3f29ceb6358d447 Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Mon, 1 May 2023 09:07:03 -0700 Subject: [PATCH] Add short-term validation test workflow (#1974) Add short-term validation test workflow --- .github/workflows/report.sh | 15 + .github/workflows/validate-1m-linux.sh | 401 +++++++++++++++++++++++++ .github/workflows/validate.yml | 57 ++++ 3 files changed, 473 insertions(+) create mode 100755 .github/workflows/report.sh create mode 100755 .github/workflows/validate-1m-linux.sh create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/report.sh b/.github/workflows/report.sh new file mode 100755 index 000000000..cc6716213 --- /dev/null +++ b/.github/workflows/report.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +################################################################################ +# Set exit code depending on tool reports # +################################################################################ + +DEFINITELY_LOST=$(cat *test-results/*summary.json | jq .num_definite_bytes_lost) + +cat *test-results/*summary.json + +echo -e "\nBytes of memory definitely lost: $DEFINITELY_LOST" + +if [[ "$DEFINITELY_LOST" -gt 0 ]]; then + exit 1 +fi diff --git a/.github/workflows/validate-1m-linux.sh b/.github/workflows/validate-1m-linux.sh new file mode 100755 index 000000000..0a8dbf4d6 --- /dev/null +++ b/.github/workflows/validate-1m-linux.sh @@ -0,0 +1,401 @@ +#!/bin/bash + +# This test script joins Earth and pokes some stuff + +TEST_NETWORK=8056c2e21c000001 +RUN_LENGTH=10 +TEST_FINISHED=false +ZTO_VER=$(git describe --tags $(git rev-list --tags --max-count=1)) +ZTO_COMMIT=$(git rev-parse HEAD) +ZTO_COMMIT_SHORT=$(git rev-parse --short HEAD) +TEST_DIR_PREFIX="$ZTO_VER-$ZTO_COMMIT_SHORT-test-results" +echo "Performing test on: $ZTO_VER-$ZTO_COMMIT_SHORT" +TEST_FILEPATH_PREFIX="$TEST_DIR_PREFIX/$ZTO_COMMIT_SHORT" +mkdir $TEST_DIR_PREFIX + +################################################################################ +# Multi-node connectivity and performance test # +################################################################################ + +NS1="ip netns exec ns1" +NS2="ip netns exec ns2" + +ZT1="$NS1 ./zerotier-cli -D$(pwd)/node1" +ZT2="$NS2 ./zerotier-cli -D$(pwd)/node2" + +echo -e "Setting up network namespaces..." +echo "Setting up ns1" + +ip netns add ns1 +$NS1 ip link set dev lo up +ip link add veth0 type veth peer name veth1 +ip link set veth1 netns ns1 +ip addr add 192.168.0.1/24 dev veth0 +ip link set dev veth0 up + +$NS1 ip addr add 192.168.0.2/24 dev veth1 +$NS1 ip link set dev veth1 up + +# Add default route +$NS1 ip route add default via 192.168.0.1 + +iptables -t nat -A POSTROUTING -s 192.168.0.0/255.255.255.0 \ + -o eth0 -j MASQUERADE +iptables -A FORWARD -i eth0 -o veth0 -j ACCEPT +iptables -A FORWARD -o eth0 -i veth0 -j ACCEPT + +echo "Setting up ns2" +ip netns add ns2 +$NS2 ip link set dev lo up +ip link add veth2 type veth peer name veth3 +ip link set veth3 netns ns2 +ip addr add 192.168.1.1/24 dev veth2 +ip link set dev veth2 up + +$NS2 ip addr add 192.168.1.2/24 dev veth3 +$NS2 ip link set dev veth3 up +$NS2 ip route add default via 192.168.1.1 + +iptables -t nat -A POSTROUTING -s 192.168.1.0/255.255.255.0 \ + -o eth0 -j MASQUERADE +iptables -A FORWARD -i eth0 -o veth2 -j ACCEPT +iptables -A FORWARD -o eth0 -i veth2 -j ACCEPT + +# Allow forwarding +sysctl -w net.ipv4.ip_forward=1 + +echo -e "\nPing from host to namespaces" + +#ping -c 4 192.168.0.1 +#ping -c 4 192.168.1.1 + +echo -e "\nPing from namespace to host" + +#$NS1 ping -c 4 192.168.0.1 +#$NS1 ping -c 4 192.168.0.1 +#$NS2 ping -c 4 192.168.0.2 +#$NS2 ping -c 4 192.168.0.2 + +echo -e "\nPing from ns1 to ns2" + +#$NS1 ping -c 4 192.168.0.1 + +echo -e "\nPing from ns2 to ns1" + +#$NS2 ping -c 4 192.168.0.1 + +################################################################################ +# Memory Leak Check # +################################################################################ + +FILENAME_MEMORY_LOG="$TEST_FILEPATH_PREFIX-memory.log" + +echo -e "\nStarting a ZeroTier instance in each namespace..." + +time_test_start=`date +%s` + +echo "Starting memory leak check" +$NS1 sudo valgrind --demangle=yes --exit-on-first-error=yes \ + --error-exitcode=1 \ + --xml=yes \ + --xml-file=$FILENAME_MEMORY_LOG \ + --leak-check=full \ + ./zerotier-one node1 >>node_1.log 2>&1 & + +# Second instance, not run in memory profiler +$NS2 ./zerotier-one node2 >>node_2.log 2>&1 & + +################################################################################ +# Online Check # +################################################################################ + +echo "Waiting for ZeroTier to come online before attempting test..." +MAX_WAIT_SECS="${MAX_WAIT_SECS:-120}" +node1_online=false +node2_online=false +both_instances_online=false +time_zt_node1_start=`date +%s` +time_zt_node2_start=`date +%s` + +for ((s=0; s<=MAX_WAIT_SECS; s++)) +do + node1_online="$($ZT1 -j info | jq '.online' 2>/dev/null)" + node2_online="$($ZT2 -j info | jq '.online' 2>/dev/null)" + if [[ "$node1_online" == "true" ]] + then + time_zt_node1_online=`date +%s` + fi + if [[ "$node2_online" == "true" ]] + then + time_zt_node2_online=`date +%s` + fi + if [[ "$node2_online" == "true" && "$node1_online" == "true" ]] + then + both_instances_online=true + break + fi + sleep 1 +done + +if [[ "$both_instances_online" != "true" ]] +then + echo "One or more instances of ZeroTier failed to come online. Aborting test." >&2 + exit 1 +fi + +echo -e "\nChecking status of each instance:" + +$ZT1 status +$ZT2 status + +echo -e "\nJoining networks" + +$ZT1 join $TEST_NETWORK +$ZT2 join $TEST_NETWORK + +sleep 10 + +node1_ip4=$($ZT1 get $TEST_NETWORK ip4) +node2_ip4=$($ZT2 get $TEST_NETWORK ip4) + +echo "node1_ip4=$node1_ip4" +echo "node2_ip4=$node2_ip4" + +echo -e "\nPinging each node" + +PING12_FILENAME="$TEST_FILEPATH_PREFIX-ping-1-to-2.txt" +PING21_FILENAME="$TEST_FILEPATH_PREFIX-ping-2-to-1.txt" + +$NS1 ping -c 16 $node2_ip4 > $PING12_FILENAME +$NS2 ping -c 16 $node1_ip4 > $PING21_FILENAME + +# Parse ping statistics +ping_loss_percent_1_to_2="${ping_loss_percent_1_to_2:-100.0}" +ping_loss_percent_2_to_1="${ping_loss_percent_2_to_1:-100.0}" + +ping_loss_percent_1_to_2=$(cat $PING12_FILENAME | \ + grep "packet loss" | awk '{print $6}' | sed 's/%//') +ping_loss_percent_2_to_1=$(cat $PING21_FILENAME | \ + grep "packet loss" | awk '{print $6}' | sed 's/%//') + +# Normalize loss value +ping_loss_percent_1_to_2=$(echo "scale=2; $ping_loss_percent_1_to_2/100.0" | bc) +ping_loss_percent_2_to_1=$(echo "scale=2; $ping_loss_percent_2_to_1/100.0" | bc) + +################################################################################ +# CLI Check # +################################################################################ + +echo "Testing basic CLI functionality..." + +# Rapidly spam the CLI with joins/leaves + +SPAM_TRIES=128 + +for ((s=0; s<=SPAM_TRIES; s++)) +do + $ZT1 join $TEST_NETWORK +done + +for ((s=0; s<=SPAM_TRIES; s++)) +do + $ZT1 leave $TEST_NETWORK +done + +for ((s=0; s<=SPAM_TRIES; s++)) +do + $ZT1 leave $TEST_NETWORK + $ZT1 join $TEST_NETWORK +done + +$ZT1 join $TEST_NETWORK + +$ZT1 -h +$ZT1 -v +$ZT1 status +$ZT1 info +$ZT1 listnetworks +$ZT1 peers +$ZT1 listpeers + +$ZT1 -j status +$ZT1 -j info +$ZT1 -j listnetworks +$ZT1 -j peers +$ZT1 -j listpeers + +$ZT1 dump + +$ZT1 get $TEST_NETWORK allowDNS +$ZT1 get $TEST_NETWORK allowDefault +$ZT1 get $TEST_NETWORK allowGlobal +$ZT1 get $TEST_NETWORK allowManaged +$ZT1 get $TEST_NETWORK bridge +$ZT1 get $TEST_NETWORK broadcastEnabled +$ZT1 get $TEST_NETWORK dhcp +$ZT1 get $TEST_NETWORK id +$ZT1 get $TEST_NETWORK mac +$ZT1 get $TEST_NETWORK mtu +$ZT1 get $TEST_NETWORK name +$ZT1 get $TEST_NETWORK netconfRevision +$ZT1 get $TEST_NETWORK nwid +$ZT1 get $TEST_NETWORK portDeviceName +$ZT1 get $TEST_NETWORK portError +$ZT1 get $TEST_NETWORK status +$ZT1 get $TEST_NETWORK type + +# Test an invalid command +$ZT1 get $TEST_NETWORK derpderp + +# TODO: Validate JSON + +################################################################################ +# Performance Test # +################################################################################ + +FILENAME_PERF_JSON="$TEST_FILEPATH_PREFIX-iperf.json" + +echo -e "\nBeginning performance test:" + +echo -e "\nStarting server:" + +echo "$NS1 iperf3 -s &" +sleep 1 + +echo -e "\nStarting client:" +sleep 1 + +echo "$NS2 iperf3 --json -c $node1_ip4 > $FILENAME_PERF_JSON" + +cat $FILENAME_PERF_JSON + +################################################################################ +# Collect ZeroTier dump files # +################################################################################ + +echo -e "\nCollecting ZeroTier dump files" + +node1_id=$($ZT1 -j status | jq -r .address) +node2_id=$($ZT2 -j status | jq -r .address) + +$ZT1 dump +mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node1_id.txt" + +$ZT2 dump +mv zerotier_dump.txt "$TEST_FILEPATH_PREFIX-node-dump-$node2_id.txt" + +################################################################################ +# Let ZeroTier idle long enough for various timers # +################################################################################ + +echo -e "\nIdling ZeroTier for $RUN_LENGTH seconds..." +sleep $RUN_LENGTH + +echo -e "\nLeaving networks" + +$ZT1 leave $TEST_NETWORK +$ZT2 leave $TEST_NETWORK + +sleep 5 + +################################################################################ +# Stop test # +################################################################################ + +echo -e "\nStopping memory check..." +sudo pkill -15 -f valgrind +sleep 10 + +time_test_end=`date +%s` + +################################################################################ +# Rename ZeroTier stdout/stderr logs # +################################################################################ + +mv node_1.log "$TEST_FILEPATH_PREFIX-node-log-$node1_id.txt" +mv node_2.log "$TEST_FILEPATH_PREFIX-node-log-$node2_id.txt" + +################################################################################ +# Generate report # +################################################################################ + +cat $FILENAME_MEMORY_LOG + +DEFINITELY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ + $FILENAME_MEMORY_LOG | grep "definitely" | awk '{print $1;}') +POSSIBLY_LOST=$(xmlstarlet sel -t -v '/valgrindoutput/error/xwhat' \ + $FILENAME_MEMORY_LOG | grep "possibly" | awk '{print $1;}') + +################################################################################ +# Generate coverage report artifact and summary # +################################################################################ + +FILENAME_COVERAGE_JSON="$TEST_FILEPATH_PREFIX-coverage.json" +FILENAME_COVERAGE_HTML="$TEST_FILEPATH_PREFIX-coverage.html" + +echo -e "\nGenerating coverage test report..." + +gcovr -r . --exclude ext --json-summary $FILENAME_COVERAGE_JSON \ + --html > $FILENAME_COVERAGE_HTML + +cat $FILENAME_COVERAGE_JSON + +COVERAGE_LINE_COVERED=$(cat $FILENAME_COVERAGE_JSON | jq .line_covered) +COVERAGE_LINE_TOTAL=$(cat $FILENAME_COVERAGE_JSON | jq .line_total) +COVERAGE_LINE_PERCENT=$(cat $FILENAME_COVERAGE_JSON | jq .line_percent) + +COVERAGE_LINE_COVERED="${COVERAGE_LINE_COVERED:-0}" +COVERAGE_LINE_TOTAL="${COVERAGE_LINE_TOTAL:-0}" +COVERAGE_LINE_PERCENT="${COVERAGE_LINE_PERCENT:-0}" + +################################################################################ +# Default values # +################################################################################ + +DEFINITELY_LOST="${DEFINITELY_LOST:-0}" +POSSIBLY_LOST="${POSSIBLY_LOST:-0}" + +################################################################################ +# Summarize and emit json for trend reporting # +################################################################################ + +FILENAME_SUMMARY="$TEST_FILEPATH_PREFIX-summary.json" + +time_length_test=$((time_test_end-time_test_start)) +time_length_zt_node1_online=$((time_zt_node1_online-time_zt_start)) +time_length_zt_node2_online=$((time_zt_node2_online-time_zt_start)) +#time_length_zt_join=$((time_zt_join_end-time_zt_join_start)) +#time_length_zt_leave=$((time_zt_leave_end-time_zt_leave_start)) +#time_length_zt_can_still_ping=$((time_zt_can_still_ping-time_zt_leave_start)) + +summary=$(cat < $FILENAME_SUMMARY +cat $FILENAME_SUMMARY + diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 000000000..665c34945 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,57 @@ +on: [ push ] + +jobs: + build_ubuntu: + runs-on: ubuntu-latest + steps: + - name: gitconfig + run: | + git config --global core.autocrlf input + + - name: checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-unknown-linux-gnu + override: true + components: rustfmt, clippy + + - name: Set up cargo cache + uses: actions/cache@v3 + continue-on-error: false + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + **/target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo- + + - name: validate-1m-linux + env: + CC: 'gcc' + CXX: 'g++' + BRANCH: ${{ github.ref_name }} + run: | + sudo apt install -y valgrind xmlstarlet gcovr iperf3 + make one ZT_COVERAGE=1 ZT_TRACE=1 + sudo chmod +x ./.github/workflows/validate-1m-linux.sh + sudo ./.github/workflows/validate-1m-linux.sh + + - name: Archive test results + uses: actions/upload-artifact@v3 + with: + name: ${{github.sha}}-test-results + path: "*test-results*" + + - name: final-report + run: | + sudo chmod +x ./.github/workflows/report.sh + sudo ./.github/workflows/report.sh