Files
spinifex/scripts/start-then-terminate-pending.sh
T
joshsiv-mulga 660fe5227e fix: idempotent tap setup + race-free terminate cleanup
Three minimal edits to close the orphan-OVS-port failure mode that
survives PR1a's systemd ordering fix (host crash, OOM-kill of ovsdb,
operator manually stopping OVS on a live host) and the
terminate-during-pending race that leaks taps when a user aborts a
slow launch.

1. SetupTapDevice (network.go): unconditional `ovs-vsctl --if-exists
   del-port br-int <tap>` + separately-gated kernel `ip tuntap del`.
   The previous kernel-side-gated cleanup missed orphan OVS ports
   because OVS conf.db survives reboot while kernel taps don't.
   Rejected `--may-exist add-port`: it would silently keep stale
   external_ids:iface-id/attached-mac from a prior launch.

2. stopInstance mgmt-tap branch (daemon.go) + CleanupMgmtTapDevice
   (network.go): derive the mgmt tap name from instance.ID rather
   than reading instance.MgmtTap, since that field is set only after
   SetupMgmtTapDevice returns and a terminate racing mid-launch
   would skip cleanup. CleanupMgmtTapDevice gains a kernel-presence
   short-circuit so unconditional invocation doesn't log spurious
   "Device does not exist" errors for instances that never reached
   SetupMgmtTapDevice.

3. LaunchInstance (daemon.go): `launchStillValid` helper re-checks
   instance status before StartInstance and TransitionState, in
   addition to the entry guard. On detection of shutting-down /
   terminated, log Info and return nil — the concurrent terminate
   goroutine owns cleanup; this is an expected race outcome, not an
   error. Removes the spurious "Failed to transition instance to
   running" ERROR that surfaced on every legitimate
   terminate-during-pending. Plus the entry path now returns nil
   instead of an error for the same reason.

Also lands `scripts/start-then-terminate-pending.sh` (added during
investigation) which reproduces the runtime race for manual
verification.

Bead: mulga-siv-20.2
Plan: docs/development/bugs/orphan-tap-on-launch-race.md (PR1b)
2026-04-27 12:33:54 +10:00

58 lines
2.1 KiB
Bash
Executable File

#!/bin/sh
# Launch instance then terminate it while still in `pending` state.
set -e
export AWS_PROFILE=spinifex
ARCH=$(uname -m)
case "$ARCH" in
x86_64) IMAGE_NAME="ami-ubuntu-24.04-x86_64" ;;
aarch64|arm64) IMAGE_NAME="ami-ubuntu-24.04-arm64" ;;
*) echo "Unknown arch $ARCH, default x86_64"; IMAGE_NAME="ami-ubuntu-24.04-x86_64" ;;
esac
INSTANCE_TYPE=$(aws ec2 describe-instance-types \
--query "InstanceTypes[?contains(InstanceType, '.micro')].InstanceType | [0]" \
--output text)
[ -z "$INSTANCE_TYPE" ] || [ "$INSTANCE_TYPE" = "None" ] && { echo "no micro type"; exit 1; }
AMI=$(aws ec2 describe-images --query "Images[?Name=='$IMAGE_NAME'].ImageId" --output text)
[ -z "$AMI" ] && { echo "no AMI $IMAGE_NAME"; exit 1; }
SUBNET=$(aws ec2 describe-subnets --query 'Subnets[0].SubnetId' --output text)
SG=$(aws ec2 describe-security-groups --query 'SecurityGroups[0].GroupId' --output text)
[ -z "$SUBNET" ] || [ "$SUBNET" = "None" ] && { echo "no subnet"; exit 1; }
[ -z "$SG" ] || [ "$SG" = "None" ] && { echo "no SG"; exit 1; }
echo "Launch: ami=$AMI type=$INSTANCE_TYPE subnet=$SUBNET sg=$SG"
INSTANCE_ID=$(aws ec2 run-instances \
--image-id "$AMI" \
--instance-type "$INSTANCE_TYPE" \
--key-name spinifex-key \
--security-group-ids "$SG" \
--subnet-id "$SUBNET" \
--count 1 \
--query 'Instances[0].InstanceId' \
--output text)
echo "Launched: $INSTANCE_ID — waiting for QEMU"
# Wait until QEMU process exists (LaunchInstance.StartInstance fired) but
# BEFORE TransitionState moves to running. Poll tight; terminate on first hit.
TIMEOUT=60
DEADLINE=$(( $(date +%s) + TIMEOUT ))
QEMU_PID=""
while [ "$(date +%s)" -lt "$DEADLINE" ]; do
QEMU_PID=$(pgrep -f "qemu.*$INSTANCE_ID" | head -1 || true)
[ -n "$QEMU_PID" ] && break
done
[ -z "$QEMU_PID" ] && { echo "ERR: QEMU never appeared"; exit 1; }
echo "QEMU pid=$QEMU_PID — terminate now"
aws ec2 terminate-instances --instance-ids "$INSTANCE_ID" \
--query 'TerminatingInstances[0].[InstanceId,PreviousState.Name,CurrentState.Name]' \
--output text