Files
cozystack/hack/e2e-apps/vminstance.bats
Myasnikov Daniil 401993ac62 test(e2e): wait for parent HR Ready before downstream asserts
Two timing bugs unmasked by Flux v2.8 kstatus, with the same root shape:
the bats steps assumed a side-effect ordering ("by the time X is true,
the parent HR's helm install is done") that held under v2.7's faster
dispatch but breaks under v2.8 where helm-controller walks kstatus on
every applied resource before flipping the HR Ready.

run-kubernetes.sh:
  After every addon HR (cilium, coredns, csi, vsnap-crd, ingress-nginx)
  reaches Ready, the remediation guard reads `.status.history` on the
  parent kubernetes-${test_name} HR. The parent's helm install can still
  be Reconciling at that point because kstatus is finishing its full
  sweep; `.status.history` is empty until install completes, so the
  guard exits 1 with "empty .status.history on a Ready HR". kubernetes-
  latest masked this because the ouroboros assertions add ~10m of
  buffer; kubernetes-previous (no ouroboros) hit the guard with the
  parent still installing. Add an explicit
    kubectl wait hr/kubernetes-${test_name} --for=condition=ready
  before the guard.

vminstance.bats:
  The test waited 120s for `kubectl get vmi vm-instance-$name` to
  return a VMI with an assigned IP, then waited for the parent HR Ready
  separately afterwards. Under v2.7 the parent HR's helm install
  applied the VirtualMachine CR fast enough that kubevirt had created
  the VMI before the 120s budget expired. Under v2.8 the dispatch is
  slower and the VMI poll returned NotFound for its entire budget.
  Reorder: wait for HR Ready first (which implies the VirtualMachine
  CR is applied + kstatus-ready), then poll for the VMI with IP.
  Loosen the VMI poll budget from 120s to 5m for nested-virt slow paths.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit c664b868bc)
2026-05-28 12:43:50 +05:00

118 lines
5.3 KiB
Bash

#!/usr/bin/env bats
@test "Create a VM Disk" {
name='test'
# Delete any leftover from a previous run and BLOCK until removal completes.
# `kubectl apply` on a resource still in finalizer-drain silently no-ops
# ("Detected changes to resource ... which is currently being deleted"),
# which then races a NotFound on the downstream HR wait. The previous
# `|| true` swallowed timeout errors here and let the test continue with
# the old VMDisk still draining. Bumped timeout to 3m and removed `|| true`
# so a true delete failure surfaces immediately.
kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --ignore-not-found --timeout=3m
kubectl -n tenant-test delete vmdisks.apps.cozystack.io $name --ignore-not-found --timeout=3m
kubectl apply -f - <<EOF
apiVersion: apps.cozystack.io/v1alpha1
kind: VMDisk
metadata:
name: $name
namespace: tenant-test
spec:
source:
http:
url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
optical: false
storage: 5Gi
storageClass: replicated
EOF
# Wait for the operator to materialise the HelmRelease before kubectl wait
# kicks in (kubectl wait errors immediately if the object does not exist yet).
timeout 60 sh -ec "until kubectl -n tenant-test get hr vm-disk-$name >/dev/null 2>&1; do sleep 2; done"
kubectl -n tenant-test wait hr vm-disk-$name --timeout=5m --for=condition=ready
kubectl -n tenant-test wait dv vm-disk-$name --timeout=250s --for=condition=ready
kubectl -n tenant-test wait pvc vm-disk-$name --timeout=200s --for=jsonpath='{.status.phase}'=Bound
# Drop the VMDisk so the next test starts from a clean slate. Each test
# owns its own resources; "Create a VM Instance" creates its own VMDisk
# inline rather than depending on this one.
kubectl -n tenant-test delete vmdisks.apps.cozystack.io $name --ignore-not-found --timeout=3m
}
@test "Create a VM Instance" {
# Use a distinct disk name from "Create a VM Disk" so a flaky finalizer
# drain on the prior disk's PV/PVC cannot collide with this test's apply.
diskName='vmi-test'
name='test'
# Pre-clean any leftover VMInstance from a prior run. The VMDisk is
# created by this test (decoupled from "Create a VM Disk" above), so no
# predelete here — the prior test already cleaned up its own VMDisk.
kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --ignore-not-found --timeout=3m
# Create the VMDisk this test depends on. Same wait-for-HR pattern as the
# first test, so this block is self-contained.
kubectl apply -f - <<EOF
apiVersion: apps.cozystack.io/v1alpha1
kind: VMDisk
metadata:
name: $diskName
namespace: tenant-test
spec:
source:
http:
url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
optical: false
storage: 5Gi
storageClass: replicated
EOF
timeout 60 sh -ec "until kubectl -n tenant-test get hr vm-disk-$diskName >/dev/null 2>&1; do sleep 2; done"
kubectl -n tenant-test wait hr vm-disk-$diskName --timeout=5m --for=condition=ready
kubectl -n tenant-test wait dv vm-disk-$diskName --timeout=250s --for=condition=ready
kubectl -n tenant-test wait pvc vm-disk-$diskName --timeout=200s --for=jsonpath='{.status.phase}'=Bound
kubectl apply -f - <<EOF
apiVersion: apps.cozystack.io/v1alpha1
kind: VMInstance
metadata:
name: $name
namespace: tenant-test
spec:
external: false
externalMethod: PortList
externalPorts:
- 22
running: true
instanceType: "u1.medium"
instanceProfile: ubuntu
disks:
- name: $diskName
gpus: []
sshKeys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPht0dPk5qQ+54g1hSX7A6AUxXJW5T6n/3d7Ga2F8gTF
test@test
cloudInit: |
#cloud-config
users:
- name: test
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD: ALL']
groups: sudo
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPht0dPk5qQ+54g1hSX7A6AUxXJW5T6n/3d7Ga2F8gTF test@test
cloudInitSeed: ""
EOF
# Wait for the operator to materialise the HelmRelease before downstream
# waits proceed (kubectl wait errors immediately if the HR does not exist).
timeout 60 sh -ec "until kubectl -n tenant-test get hr vm-instance-$name >/dev/null 2>&1; do sleep 2; done"
# Wait for the parent HR Ready before polling for the VMI. Under Flux v2.8
# kstatus the helm install can be still dispatching when the previous
# ordering assumed it had already applied the VirtualMachine CR — without
# this the next poll for `vmi vm-instance-$name` returned NotFound for
# its entire 120s budget because kubevirt hadn't seen the VM yet.
kubectl -n tenant-test wait hr vm-instance-$name --timeout=5m --for=condition=ready
# Nested KubeVirt VM startup (virt-launcher + libvirt + cloud-init DHCP)
# routinely takes 30-60s under runner load. 5m is a comfortable upper
# bound for nested virt + slow runner I/O after the HR-Ready gate.
timeout 5m sh -ec "until kubectl -n tenant-test get vmi vm-instance-$name -o jsonpath='{.status.interfaces[0].ipAddress}' | grep -q '[0-9]'; do sleep 2; done"
# VM ready follows IP assignment closely; 60s gives buffer for the qemu-guest-agent.
kubectl -n tenant-test wait vm vm-instance-$name --timeout=5m --for=condition=ready
kubectl -n tenant-test delete vminstances.apps.cozystack.io $name --ignore-not-found --timeout=3m
kubectl -n tenant-test delete vmdisks.apps.cozystack.io $diskName --ignore-not-found --timeout=3m
}