mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-29 08:07:33 +09:00
Dropping the HotplugVolumes gate lets virt-controller rebuild each running VMI's hotplug volumes from VM.spec, and it removes every hotpluggable VMI volume the VM spec does not list. kubevirt-csi attached tenant volumes at the VMI level until mid-2025 and live migration carries such a VMI across KubeVirt updates, so a tenant node that was never recreated can still hold a mounted disk only its VMI knows about; the first reconcile after the upgrade would unplug it. Migration 59 writes each such volume and its disk into the VM template as they are on the VMI, before the upgrade re-points cozystack-packages and so before the KubeVirt CR and the kubevirt-csi flag change. Hotplug volume changes are a valid live update, so no VM needs a restart. An ephemeral hotplug a user made at the VMI level, such as virtctl addvolume without --persist, has the same shape, because virt-api gives every VMI-level addvolume that shape. It is copied too and becomes permanent: leaving it out would not keep it ephemeral, since the first reconcile would unplug it. Only Running VMIs are considered, matching virt-controller, which reconciles no other; a stopped VM's next start builds its VMI from the VM spec, so its VMI-only volumes are not resurrected. Only the variants with migrations enabled run it. The default variant renders no migration hook, so a cluster there that installed KubeVirt by hand has to check for such volumes itself before upgrading. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la>
94 lines
4.0 KiB
Bash
Executable File
94 lines
4.0 KiB
Bash
Executable File
#!/bin/sh
|
|
# Fake kubectl for hack/migration-59-vmi-hotplug-volumes.bats. It serves only
|
|
# the calls migration 59 and lib/cozystack-version.sh make, and logs each one
|
|
# to $FAKE_CMDLOG.
|
|
#
|
|
# The VMs live in $FAKE_STATE/vms.json and a JSON patch is applied to that
|
|
# file, so a second run of the migration reads what the first one wrote.
|
|
#
|
|
# FAKE_CMDLOG file to append a one-line record of each call to
|
|
# FAKE_STATE directory holding vms.json and vmis.json
|
|
# FAKE_NO_KUBEVIRT non-empty => the VirtualMachine CRD is absent
|
|
# FAKE_CRD_FAIL non-empty => reading the VirtualMachine CRD exits 1
|
|
# FAKE_LIST_FAIL non-empty => listing VMs exits 1 with this stderr
|
|
# FAKE_VMI_LIST_FAIL non-empty => listing VMIs exits 1 with this stderr
|
|
# FAKE_PATCH_FAIL non-empty => every patch exits 1 with this stderr
|
|
set -u
|
|
|
|
log() { [ -n "${FAKE_CMDLOG:-}" ] && printf '%s\n' "$*" >> "$FAKE_CMDLOG"; }
|
|
log "KUBECTL $*"
|
|
|
|
args="$*"
|
|
arg_after() { k="$1"; shift; p=""; for a in "$@"; do [ "$p" = "$k" ] && { printf '%s' "$a"; return; }; p="$a"; done; }
|
|
|
|
case "$args" in
|
|
*"apply --filename -"*|*"apply -f -"*)
|
|
manifest=$(cat)
|
|
case "$manifest" in
|
|
*"kind: ConfigMap"*)
|
|
v=$(printf '%s\n' "$manifest" | sed -n 's/^ version: "\{0,1\}\([0-9]\{1,\}\)"\{0,1\}$/\1/p' | head -1)
|
|
log "STAMP ${v:-unknown}"
|
|
exit 0
|
|
;;
|
|
esac
|
|
log "APPLY-UNKNOWN"
|
|
printf 'fake kubectl: unmodelled apply\n' >&2
|
|
exit 1
|
|
;;
|
|
|
|
*"get crd virtualmachines.kubevirt.io"*)
|
|
if [ -n "${FAKE_CRD_FAIL:-}" ]; then printf '%s\n' "$FAKE_CRD_FAIL" >&2; exit 1; fi
|
|
[ -n "${FAKE_NO_KUBEVIRT:-}" ] || echo "customresourcedefinition.apiextensions.k8s.io/virtualmachines.kubevirt.io"
|
|
exit 0
|
|
;;
|
|
|
|
*"get virtualmachines.kubevirt.io"*)
|
|
if [ -n "${FAKE_LIST_FAIL:-}" ]; then printf '%s\n' "$FAKE_LIST_FAIL" >&2; exit 1; fi
|
|
cat "$FAKE_STATE/vms.json" || exit 1
|
|
exit 0
|
|
;;
|
|
|
|
*"get virtualmachineinstances.kubevirt.io"*)
|
|
if [ -n "${FAKE_VMI_LIST_FAIL:-}" ]; then printf '%s\n' "$FAKE_VMI_LIST_FAIL" >&2; exit 1; fi
|
|
cat "$FAKE_STATE/vmis.json" || exit 1
|
|
exit 0
|
|
;;
|
|
|
|
*"patch virtualmachines.kubevirt.io"*)
|
|
if [ -n "${FAKE_PATCH_FAIL:-}" ]; then printf '%s\n' "$FAKE_PATCH_FAIL" >&2; exit 1; fi
|
|
ns=$(arg_after -n "$@")
|
|
name=$(arg_after virtualmachines.kubevirt.io "$@")
|
|
patch=$(arg_after -p "$@")
|
|
# Refuse a stale resourceVersion the way the API server does, then apply
|
|
# every add op and bump the resourceVersion.
|
|
rv=$(jq -r --arg ns "$ns" --arg n "$name" \
|
|
'.items[] | select(.metadata.namespace == $ns and .metadata.name == $n) | .metadata.resourceVersion' \
|
|
"$FAKE_STATE/vms.json")
|
|
want=$(printf '%s' "$patch" | jq -r '.[] | select(.op == "test" and .path == "/metadata/resourceVersion") | .value')
|
|
if [ -z "$want" ] || [ "$want" != "$rv" ]; then
|
|
printf 'Error from server (Conflict): test failed\n' >&2
|
|
exit 1
|
|
fi
|
|
jq --arg ns "$ns" --arg n "$name" --argjson p "$patch" '
|
|
.items |= map(
|
|
if .metadata.namespace == $ns and .metadata.name == $n then
|
|
reduce ($p[] | select(.op == "add")) as $op (.;
|
|
setpath($op.path | ltrimstr("/") | split("/"); $op.value))
|
|
| .metadata.resourceVersion = ((.metadata.resourceVersion | tonumber) + 1 | tostring)
|
|
else . end)' "$FAKE_STATE/vms.json" > "$FAKE_STATE/vms.json.new"
|
|
mv "$FAKE_STATE/vms.json.new" "$FAKE_STATE/vms.json"
|
|
vols=$(jq -r --arg ns "$ns" --arg n "$name" \
|
|
'.items[] | select(.metadata.namespace == $ns and .metadata.name == $n) | [.spec.template.spec.volumes[].name] | join(",")' \
|
|
"$FAKE_STATE/vms.json")
|
|
disks=$(jq -r --arg ns "$ns" --arg n "$name" \
|
|
'.items[] | select(.metadata.namespace == $ns and .metadata.name == $n) | [.spec.template.spec.domain.devices.disks[].name] | join(",")' \
|
|
"$FAKE_STATE/vms.json")
|
|
log "PATCH-VM $ns/$name volumes=$vols disks=$disks"
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
log "UNHANDLED $args"
|
|
printf 'fake kubectl: unmodelled call: %s\n' "$args" >&2
|
|
exit 1
|