mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-21 11:46:37 +00:00
9c9bb9430c
* test: gate scripts/images/ shell suites + shellcheck in CI The eks-node/rds-postgres *_test.sh suites and shellcheck only ever ran by hand: spinifex.yml filters pushes on Go-only paths and no job invoked them, so a broken helper could land unnoticed. Add a `make test-images` target and a path-filtered image-scripts.yml workflow so both run automatically on scripts/images/** and images/** changes, kept separate from preflight so image-asset churn doesn't run on every Go contributor's commit. Also clears the shellcheck findings that would have started this gate red (SC1090 on a runtime-resolved source path, SC2154 x2 on eval-assigned variables shellcheck can't trace). * test: pin vCPU quota gate ordering in RequestSpotInstances Nothing asserted that RequestSpotInstances runs the vCPU quota gate before dispatching capacity or launching a VM, so a regression that dropped or reordered EnforceLaunch would pass silently. Add an ordering test using a real quota.Service (embedded JetStream KV) and real NATS responders standing in for the capacity and launch daemons, recording a global call sequence and asserting the gate's index precedes both. Verified by removing the gate: the first quota-KV read then comes from the post-launch charge instead, landing after capacity dispatch and launch, and the ordering assertion fails as expected.
130 lines
5.5 KiB
Bash
130 lines
5.5 KiB
Bash
#!/bin/sh
|
|
# mulga-mgmt-net — boot oneshot that brings a multi-NIC BootAMI system VM's NICs
|
|
# up from the QEMU fw_cfg netcfg blob (opt/spinifex/netcfg) the host writes per
|
|
# launch. The EKS control plane is the case: cloud-init on a stock Alpine guest
|
|
# cannot reliably pick the right NIC out of two, so the host enumerates them and
|
|
# this oneshot configures each by MAC, before cloud-init's network stage:
|
|
# - NIC<n>_DHCP=1: the primary data ENI. OVN serves DHCP; we lease it (retrying
|
|
# one-shots on a budget until the cross-host datapath is up) so the Ec2 IMDS
|
|
# datasource can reach 169.254.169.254, and pin a /32 to it so a link-local
|
|
# 169.254.0.0/16 route on another NIC cannot hijack IMDS.
|
|
# - NIC<n>_CIDR: a static NIC (mgmt0 on br-mgmt, which has no DHCP). Applied
|
|
# address-only; NIC<n>_DEFAULT is 0, so mgmt0 is never the default route.
|
|
#
|
|
# The blob is shell KEY=value, matching daemon.buildNetcfgBlob and
|
|
# build/microvm/init.sh; interfaces are matched by MAC. No-op when the blob is
|
|
# absent (a single-NIC agent/worker brings its one NIC up via cloud-init/IMDS),
|
|
# so the same image boots unchanged without one.
|
|
set -eu
|
|
|
|
NETCFG="${MULGA_NETCFG:-/sys/firmware/qemu_fw_cfg/by_name/opt/spinifex/netcfg/raw}"
|
|
IMDS_IP="169.254.169.254"
|
|
|
|
# qemu_fw_cfg is a module on the stock Alpine cloud image; load it (best-effort)
|
|
# before reading. Skipped harmlessly when built-in or already loaded.
|
|
modprobe qemu_fw_cfg 2>/dev/null || modprobe fw_cfg_sysfs 2>/dev/null || true
|
|
|
|
if [ ! -f "$NETCFG" ]; then
|
|
echo "[mulga-mgmt-net] no fw_cfg netcfg; skipping system NIC setup"
|
|
exit 0
|
|
fi
|
|
|
|
# shellcheck disable=SC1090
|
|
. "$NETCFG"
|
|
|
|
# One-shot DHCP: configure the link then exit, leaving no daemon to fight
|
|
# cloud-init's later management of the primary ENI. Prefer busybox udhcpc; fall
|
|
# back to dhcpcd where udhcpc's config script is absent.
|
|
dhcp_oneshot() {
|
|
iface="$1"
|
|
if command -v udhcpc >/dev/null 2>&1 && [ -x /usr/share/udhcpc/default.script ]; then
|
|
udhcpc -i "$iface" -f -q -n -t 8 -T 2 >/dev/null 2>&1
|
|
elif command -v dhcpcd >/dev/null 2>&1; then
|
|
dhcpcd -q -t 20 "$iface" >/dev/null 2>&1
|
|
else
|
|
echo "[mulga-mgmt-net] no DHCP client (udhcpc/dhcpcd) for $iface" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# The OVN DHCP datapath for a cross-host CP data NIC lags guest boot by minutes
|
|
# (logical-port binding + per-tap reconcile on the remote placement host). A
|
|
# single ~16s one-shot loses that race and strands the NIC IP-less, so the Ec2
|
|
# datasource never reaches IMDS and the member wedges. Retry the one-shot on a
|
|
# budget aligned with cloud-init's Ec2 max_wait (600s); each attempt still exits
|
|
# leaving no lingering daemon, so cloud-init's later ENI management is unaffected.
|
|
DHCP_BUDGET="${MULGA_DHCP_BUDGET:-600}"
|
|
|
|
dhcp_acquire() {
|
|
iface="$1"
|
|
deadline=$(( $(date +%s) + DHCP_BUDGET ))
|
|
while :; do
|
|
if dhcp_oneshot "$iface" && ip -4 addr show dev "$iface" | grep -q 'inet '; then
|
|
return 0
|
|
fi
|
|
[ "$(date +%s)" -ge "$deadline" ] && return 1
|
|
sleep 3
|
|
done
|
|
}
|
|
|
|
for n in 0 1 2 3 4 5; do
|
|
# ${VAR:-} keeps the unset NIC<n>_* slots safe under `set -u`.
|
|
eval "mac=\${NIC${n}_MAC:-}"
|
|
[ -z "$mac" ] && continue
|
|
|
|
iface=$(ip -o link | awk -v m="$mac" 'tolower($0) ~ tolower(m) {print $2}' | tr -d :)
|
|
if [ -z "$iface" ]; then
|
|
echo "[mulga-mgmt-net] WARNING: no interface for MAC $mac (NIC$n); skipping" >&2
|
|
continue
|
|
fi
|
|
|
|
eval "dhcp=\${NIC${n}_DHCP:-}"
|
|
eval "cidr=\${NIC${n}_CIDR:-}"
|
|
ip link set "$iface" up
|
|
|
|
# dhcp is assigned above via eval; shellcheck can't trace dynamic names.
|
|
# shellcheck disable=SC2154
|
|
if [ "$dhcp" = "1" ]; then
|
|
if dhcp_acquire "$iface"; then
|
|
echo "[mulga-mgmt-net] data NIC $iface ($mac) up via DHCP"
|
|
else
|
|
echo "[mulga-mgmt-net] ERROR: no DHCP lease on data NIC $iface ($mac) after ${DHCP_BUDGET}s" >&2
|
|
fi
|
|
# Pin IMDS to the data NIC so a link-local 169.254.0.0/16 route on another
|
|
# interface cannot steal the metadata path. Route via the gateway, not
|
|
# on-link: the host demuxes IMDS sent to the gateway MAC, never ARP-answers .254.
|
|
gw=$(ip -4 route show default dev "$iface" 2>/dev/null | awk '{print $3; exit}')
|
|
if [ -n "$gw" ]; then
|
|
ip route replace "${IMDS_IP}/32" via "$gw" dev "$iface" 2>/dev/null || true
|
|
else
|
|
ip route replace "${IMDS_IP}/32" dev "$iface" 2>/dev/null || true
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
if [ -z "$cidr" ]; then
|
|
echo "[mulga-mgmt-net] brought up $iface ($mac), no CIDR"
|
|
continue
|
|
fi
|
|
# `replace` is idempotent — adds the address if absent, no-op on a stop/start
|
|
# re-attach that reuses a surviving interface. A real failure must surface,
|
|
# not strand mgmt0 IP-less.
|
|
if ip addr replace "$cidr" dev "$iface"; then
|
|
echo "[mulga-mgmt-net] configured $iface ($mac) with $cidr"
|
|
else
|
|
echo "[mulga-mgmt-net] ERROR: failed to set $cidr on $iface ($mac)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# NIC<n>_DEFAULT=0 means this NIC must never carry the default route: the
|
|
# mgmt NIC reaches the gateway on-link and a default via it would blackhole
|
|
# egress and (with a link-local /16) hijack IMDS. Enforce it — a DHCP client
|
|
# racing this NIC may have added one before we set it static.
|
|
eval "isdefault=\${NIC${n}_DEFAULT:-}"
|
|
# isdefault is assigned above via eval; shellcheck can't trace dynamic names.
|
|
# shellcheck disable=SC2154
|
|
if [ "$isdefault" != "1" ]; then
|
|
ip route del default dev "$iface" 2>/dev/null || true
|
|
fi
|
|
done
|