diff --git a/.github/actions/truenas-appliance-runner/action.yml b/.github/actions/truenas-appliance-runner/action.yml index e533d1f..e871e46 100644 --- a/.github/actions/truenas-appliance-runner/action.yml +++ b/.github/actions/truenas-appliance-runner/action.yml @@ -69,6 +69,13 @@ inputs: alike cannot give, such as a disk big enough to mirror the boot pool onto. Not for an HA pair. default: '' + spare-nics: + description: >- + NICs besides the one the API is reached on, 0 to 9, each alone on a + qemu hub: no DHCP, no route, nothing on the other end. For a suite to + apply interface changes on without cutting itself off. Their MACs, + 52:54:00:e2:e0:01 and up, are in TN_SPARE_NICS. Not for an HA pair. + default: '0' ssh: description: '`true` enables ssh for the admin, on TN_SSH.' default: 'false' @@ -114,6 +121,7 @@ runs: APPLIANCE_DATA_DISKS: ${{ inputs.data-disk-count }} APPLIANCE_DATA_DISK_GB: ${{ inputs.data-disk-gb }} APPLIANCE_EXTRA_DISK_GB: ${{ inputs.extra-disk-gb }} + APPLIANCE_SPARE_NICS: ${{ inputs.spare-nics }} APPLIANCE_SSH: ${{ inputs.ssh }} APPLIANCE_DISK_INPUT: ${{ inputs.disk }} EXPORT_ENV: ${{ inputs.export-env }} @@ -128,6 +136,10 @@ runs: echo "::error::extra-disk-gb is for one appliance; an HA pair has only the data-disk-count disks its nodes share" exit 1 fi + if [ "$HA" = true ] && [ "${APPLIANCE_SPARE_NICS:-0}" != 0 ]; then + echo "::error::spare-nics is for one appliance; ha.sh wires an HA pair's NICs itself" + exit 1 + fi export APPLIANCE_DIR="$RUNNER_TEMP/truenas-appliance" # The password is made here, so it is masked before anything can # print it. diff --git a/.github/actions/truenas-appliance-runner/appliance.sh b/.github/actions/truenas-appliance-runner/appliance.sh index c49e4cb..cf51f4c 100755 --- a/.github/actions/truenas-appliance-runner/appliance.sh +++ b/.github/actions/truenas-appliance-runner/appliance.sh @@ -37,6 +37,10 @@ # APPLIANCE_DATA_DISKS 10 APPLIANCE_DATA_DISK_GB 10 (sparse) # APPLIANCE_EXTRA_DISK_GB more sparse disks after those, one per size in # GB, space-separated: "32", "12 12 15" (default none) +# APPLIANCE_SPARE_NICS 0: NICs after the one the API is reached on, each +# alone on a qemu hub of its own, so no DHCP, no +# route and no peer: for a suite to reconfigure +# without cutting itself off # APPLIANCE_HTTP_PORT 8080 APPLIANCE_HTTPS_PORT 8443 # APPLIANCE_SSH_PORT 2222 APPLIANCE_SSH false # APPLIANCE_BASELINE fresh-install: the snapshot start takes; empty @@ -49,7 +53,8 @@ # start prints TN_PROFILE, TN_HOST (host:https port), TN_HOST_HTTP, # TN_USERNAME, TN_PASSWORD, TN_DOMAIN, TN_BASELINE (the suite's contract, # as the lab's claim action gives it, less its TN_GUEST_ISO), TN_SSH when -# APPLIANCE_SSH is true, and what it measured: +# APPLIANCE_SSH is true, TN_SPARE_NICS (the spare NICs' MAC addresses, +# space-separated) when there are any, and what it measured: # APPLIANCE_BOOT_SECONDS, APPLIANCE_SNAPSHOT_SECONDS. set -euo pipefail @@ -101,6 +106,7 @@ start() { local ndata="${APPLIANCE_DATA_DISKS:-10}" datagb="${APPLIANCE_DATA_DISK_GB:-10}" local extragb=() read -r -a extragb <<< "${APPLIANCE_EXTRA_DISK_GB:-}" + local nspare="${APPLIANCE_SPARE_NICS:-0}" local http="${APPLIANCE_HTTP_PORT:-8080}" https="${APPLIANCE_HTTPS_PORT:-8443}" sshp="${APPLIANCE_SSH_PORT:-2222}" local baseline="${APPLIANCE_BASELINE-fresh-install}" local password="${APPLIANCE_PASSWORD:-$(head -c 15 /dev/urandom | base64 | tr -d '/+=')}" @@ -111,6 +117,7 @@ start() { for gb in "${extragb[@]}"; do [[ "$gb" =~ ^[1-9][0-9]*$ ]] || die "APPLIANCE_EXTRA_DISK_GB: \"$gb\" is not a whole number of GB" done + [[ "$nspare" =~ ^[0-9]$ ]] || die "APPLIANCE_SPARE_NICS: \"$nspare\" is not a number of NICs from 0 to 9" needs qemu-system-x86_64 qemu-system-x86 qemu-img qemu-utils python3 python3 ovmf local accel="${APPLIANCE_ACCEL:-kvm}" cpu=host @@ -157,9 +164,26 @@ start() { extrasaid=", and $nextra more of $(printf '%sG ' "${extragb[@]}")" extrasaid="${extrasaid% }" fi + # After the NIC the API is reached on, so that one keeps its PCI slot, + # and with it its name, whatever the count. Each on a hub with no other + # port: its link is up, and nothing ever answers on it. qemu warns + # "hub N is not connected to host network" for each, which is the + # point. The MACs are fixed, for a suite to tell these NICs apart by. + local nics=() spare_macs=() + for i in $(seq 1 "$nspare"); do + spare_macs+=("52:54:00:e2:e0:0$i") + nics+=(-netdev "hubport,id=spare$i,hubid=$i" + -device "virtio-net-pci,netdev=spare$i,mac=52:54:00:e2:e0:0$i") + done + local nicssaid="" + if [ "$nspare" -eq 1 ]; then + nicssaid=", 1 spare NIC" + elif [ "$nspare" -gt 1 ]; then + nicssaid=", $nspare spare NICs" + fi local booted t0 - say "booting $disk as $name: ${memory} MB, $vcpus vCPUs, $ndata data disks of ${datagb}G$extrasaid" + say "booting $disk as $name: ${memory} MB, $vcpus vCPUs, $ndata data disks of ${datagb}G$extrasaid$nicssaid" t0=$(date +%s.%N) qemu-system-x86_64 -name "$name" -machine "q35,accel=$accel" -cpu "$cpu" -smp "$vcpus" -m "$memory" \ -smbios "type=1,serial=$name" \ @@ -169,6 +193,7 @@ start() { -device "virtio-blk-pci,drive=os,serial=os,bootindex=0" \ "${drives[@]}" \ -nic "user,model=virtio-net-pci$(hostfwd 80 "$http")$(hostfwd 443 "$https")$(hostfwd 22 "$sshp")" \ + "${nics[@]}" \ -device virtio-rng-pci \ -qmp "unix:$QMP,server=on,wait=off" \ -display none -serial "file:$DIR/console.log" \ @@ -222,6 +247,9 @@ EOF if [ "${APPLIANCE_SSH:-false}" = true ]; then echo "TN_SSH=127.0.0.1:$sshp" fi + if [ "$nspare" -gt 0 ]; then + echo "TN_SPARE_NICS=${spare_macs[*]}" + fi } snapshot() { diff --git a/README.md b/README.md index e5cccd8..d92b18a 100644 --- a/README.md +++ b/README.md @@ -555,6 +555,7 @@ jobs: | `memory-mb`, `vcpus` | `6144`, `4` | The appliance's; the runner's flavor needs room for it and the job | | `data-disk-count`, `data-disk-gb` | `10`, `10` | Sparse qcow2 files | | `extra-disk-gb` | none | More sparse disks after the data disks, one per size in GB: `'32'`, `'12 12 15'`. Not for an HA pair | +| `spare-nics` | `'0'` | NICs after the one the API is reached on, each alone on a qemu hub: no DHCP, no route. For applying interface changes without losing the appliance. MACs `52:54:00:e2:e0:01` and up, in `TN_SPARE_NICS`. Not for an HA pair | | `ssh` | `'false'` | `true` enables ssh for the admin, on `TN_SSH` | | `disk` | the attached one | Found by its serial, `virtio-truenas_appliance`, where the box keeps it; TrueNAS replaces it, so otherwise the one disk besides the root disk that holds a ZFS `boot-pool` | | `export-env` | `'true'` | Write `TN_*` to the job environment |