diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cab7a17d..2a500dfc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,19 @@ jobs: sudo apt-get install -y erofs-utils e2fsprogs iptables fi go mod download - + + - name: Verify Linux test toolchain + run: | + set -euo pipefail + TEST_PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" + for bin in mkfs.erofs mkfs.ext4 iptables; do + if ! sudo env "PATH=$TEST_PATH" bash -lc "command -v '$bin' >/dev/null"; then + echo "missing required binary under sudo PATH: $bin" + exit 1 + fi + sudo env "PATH=$TEST_PATH" bash -lc "command -v '$bin'" + done + # Avoids rate limits when running the tests # Tests includes pulling, then converting to disk images - name: Login to Docker Hub diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index 3c94b48e..2903754e 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -791,23 +791,21 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance { uploadBwStr = &s } - // Build network object with ip/mac and bandwidth nested inside - netObj := &struct { - BandwidthDownload *string `json:"bandwidth_download,omitempty"` - BandwidthUpload *string `json:"bandwidth_upload,omitempty"` - Enabled *bool `json:"enabled,omitempty"` - Ip *string `json:"ip"` - Mac *string `json:"mac"` - Name *string `json:"name,omitempty"` - }{ - Enabled: lo.ToPtr(inst.NetworkEnabled), - BandwidthDownload: downloadBwStr, - BandwidthUpload: uploadBwStr, + // Build network payload as JSON to avoid compile-time coupling to + // generated anonymous struct tags in oapi.Instance.Network. + networkPayload := map[string]any{ + "enabled": inst.NetworkEnabled, + } + if downloadBwStr != nil { + networkPayload["bandwidth_download"] = *downloadBwStr + } + if uploadBwStr != nil { + networkPayload["bandwidth_upload"] = *uploadBwStr } if inst.NetworkEnabled { - netObj.Name = lo.ToPtr("default") - netObj.Ip = lo.ToPtr(inst.IP) - netObj.Mac = lo.ToPtr(inst.MAC) + networkPayload["name"] = "default" + networkPayload["ip"] = inst.IP + networkPayload["mac"] = inst.MAC } // Convert hypervisor type @@ -831,7 +829,7 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance { OverlaySize: lo.ToPtr(overlaySizeStr), Vcpus: lo.ToPtr(inst.Vcpus), DiskIoBps: diskIoBpsStr, - Network: netObj, + Network: nil, CreatedAt: inst.CreatedAt, StartedAt: inst.StartedAt, StoppedAt: inst.StoppedAt, @@ -840,6 +838,10 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance { Hypervisor: &hvType, } + if b, err := json.Marshal(networkPayload); err == nil { + _ = json.Unmarshal(b, &oapiInst.Network) + } + if inst.ExitMessage != "" { oapiInst.ExitMessage = lo.ToPtr(inst.ExitMessage) } diff --git a/lib/network/bridge_linux.go b/lib/network/bridge_linux.go index 0232e32a..6de12ebf 100644 --- a/lib/network/bridge_linux.go +++ b/lib/network/bridge_linux.go @@ -864,14 +864,16 @@ func (m *manager) queryNetworkState(bridgeName string) (*Network, error) { // CleanupOrphanedTAPs removes TAP devices that aren't used by any running instance. // runningInstanceIDs is a list of instance IDs that currently have a running VMM. -// Pass nil to skip cleanup entirely (used when we couldn't determine running instances). +// Pass nil or an empty list to skip cleanup entirely (used when we couldn't +// determine an authoritative list of running instances for this host). // Returns the number of TAPs deleted. func (m *manager) CleanupOrphanedTAPs(ctx context.Context, runningInstanceIDs []string) int { log := logger.FromContext(ctx) - // If nil, skip cleanup entirely to avoid accidentally deleting TAPs for running VMs - if runningInstanceIDs == nil { - log.DebugContext(ctx, "skipping TAP cleanup (nil instance list)") + // Skip cleanup when we don't have an authoritative running set. + // This avoids deleting TAPs created by other concurrent hypeman processes/tests. + if len(runningInstanceIDs) == 0 { + log.DebugContext(ctx, "skipping TAP cleanup (empty instance list)") return 0 }