mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
test(e2e): record the runner's SVM feature flags in the identity capture
Adds one line to the runner identity record, taken from the lscpu read it
already does:
svm features: svm npt nrip_save vgif
vgif decides whether a guest that is itself running a hypervisor pays a VM exit
on every CLGI/STGI. Here that is the sandbox Talos nodes hosting the tenant
KubeVirt VMs, so it only bites at double nesting, and the sandbox nodes' own
boot is unaffected.
Across the archived reports no run missing the flag has passed a
tenant-Kubernetes suite: 0 of 62, against 11 of 12 with it. Exposure varies
between hosts of the same shape, so it is not a property of the lane and cannot
be recovered once the runner is recycled. Recording it on every run, red and
green, is what makes the correlation countable rather than reconstructed by hand.
No new bounded read, no compiler on the runner, no sysfs. Gated on svm so an
Intel host's vnmi is not reported as an SVM feature.
Five tests for the reader. Identity suite 62 assertions, hack/ sweep 732.
No behaviour change and no gating on the flag.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
This commit is contained in:
2 files changed
+146
No files matched your search
@@ -461,6 +461,57 @@ runner_cpu_model() {
|
||||
sed -n 's/^Model name:[[:space:]]*//p' | sed -n '1p'
|
||||
}
|
||||
|
||||
# runner_svm_features: the AMD SVM sub-features this VM's virtual CPU was given.
|
||||
#
|
||||
# Same shape as the model reader above and for the same reason: it transforms an
|
||||
# `lscpu` table handed to it on stdin, so the one bounded read stays at the one
|
||||
# call site that owns the ceiling.
|
||||
#
|
||||
# WHY THIS LINE IS WORTH A PLACE IN THE RECORD
|
||||
#
|
||||
# `svm` says a guest may run a hypervisor. The sub-features say what that costs.
|
||||
# The one that has mattered here is `vgif`: without it, a guest that is itself
|
||||
# running a hypervisor traps to the layer below on every CLGI and STGI, and those
|
||||
# bracket every world switch it performs. A guest that runs no hypervisor never
|
||||
# executes either instruction, so this is invisible until something nests two
|
||||
# deep -- which is exactly this suite, where the sandbox Talos nodes host the
|
||||
# tenant KubeVirt VMs. It explains a shape of failure no resource counter can:
|
||||
# the cycles are spent handling exits rather than running the guest, so the
|
||||
# runner reads mostly idle with near-zero steal while its nested guests freeze.
|
||||
#
|
||||
# Measured across the archived reports, no run whose kernel did not report
|
||||
# Virtual GIF has ever passed a tenant-Kubernetes suite, and the two hosts
|
||||
# compared directly differed in this one flag and nothing else -- same shape,
|
||||
# same processor model, same kernel. On a flex shape filled from shared capacity
|
||||
# that is not a property of the lane, so it belongs beside the shape and the
|
||||
# processor rather than in a reader's memory of a run that has since been
|
||||
# recycled.
|
||||
#
|
||||
# The whole SVM list is kept rather than that one flag, because which features a
|
||||
# host exposes has turned out to vary between hosts of the same shape, and a
|
||||
# record that answered only the question already asked would have to be extended
|
||||
# by whoever asks the next one.
|
||||
#
|
||||
# Names are the kernel's own from this leaf, matched whole so `svm` cannot be
|
||||
# taken from `svm_lock`; `-E` and `-x` are both POSIX. `grep` finding nothing is
|
||||
# a status this reads as an answer, so no branch here -- an Intel host, or a
|
||||
# guest given no sub-features, prints an empty list and the caller says so.
|
||||
runner_svm_features() {
|
||||
_rsf_flags=$(sed -n 's/^Flags:[[:space:]]*//p' | sed -n '1p')
|
||||
# Gated on `svm` itself, and not only for tidiness: `vnmi` is a flag name on
|
||||
# Intel too, where it belongs to VMX, so an ungated list prints one SVM
|
||||
# sub-feature on a machine that has no SVM at all. Sub-features of an absent
|
||||
# feature are not a shorter answer, they are a wrong one. Padded case rather
|
||||
# than a grep so `svm` cannot be taken from `svm_lock`.
|
||||
case " $_rsf_flags " in
|
||||
*' svm '*) ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
printf '%s\n' "$_rsf_flags" | tr ' ' '\n' |
|
||||
grep -xE 'svm|svm_lock|npt|lbrv|nrip_save|tsc_scale|vmcb_clean|flushbyasid|decodeassists|pausefilter|pfthreshold|avic|x2avic|v_vmsave_vmload|vgif|vnmi|v_spec_ctrl|svme_addr_chk' |
|
||||
tr '\n' ' ' | sed 's/[[:space:]]*$//'
|
||||
}
|
||||
|
||||
if [ -n "${E2E_RUNNER_IDENTITY_LIB:-}" ]; then
|
||||
return 0 2>/dev/null
|
||||
fi
|
||||
@@ -646,8 +697,20 @@ if runner_read lscpu; then
|
||||
else
|
||||
add 'cpu model: unavailable (lscpu answered, and no Model name line was taken from its table)'
|
||||
fi
|
||||
# Out of the same read as the model, so the two lines describe one reading of
|
||||
# one machine rather than two that a slow first read could have separated.
|
||||
svm_features=$(printf '%s\n' "$RUNNER_READ_OUT" | runner_svm_features)
|
||||
if [ -n "$svm_features" ]; then
|
||||
add "svm features: $svm_features${RUNNER_READ_WHY:+ ($RUNNER_READ_WHY)}"
|
||||
else
|
||||
# Distinguished from a missing line on purpose: an empty list is a fact about
|
||||
# the machine -- no SVM sub-feature was exposed to it -- and a reader who
|
||||
# cannot tell that from a read that never happened learns nothing from either.
|
||||
add 'svm features: none (lscpu answered, and its flag list named no SVM sub-feature; an Intel host, or a guest given none)'
|
||||
fi
|
||||
else
|
||||
add "cpu model: unavailable ($RUNNER_READ_WHY)"
|
||||
add "svm features: unavailable ($RUNNER_READ_WHY)"
|
||||
fi
|
||||
|
||||
# The file first, the log second, and neither conditional on the other. The file
|
||||
|
||||
@@ -251,11 +251,94 @@ stage() {
|
||||
{
|
||||
printf 'Architecture: x86_64\n'
|
||||
printf 'Model name: AMD EPYC 9J14 96-Core Processor\n'
|
||||
printf 'Flags: fpu vme de pse tsc msr svm npt nrip_save vgif umip pku\n'
|
||||
} >"$1/lscpu.out"
|
||||
printf '32\n' >"$1/nproc.out"
|
||||
printf '6.8.0-1021-oracle\n' >"$1/uname.out"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The SVM sub-feature reader, driven directly.
|
||||
#
|
||||
# These exist because the flag this reader was added for, vgif, is the one field
|
||||
# in the record that decides whether a nested guest runs at hardware speed or
|
||||
# traps to the layer below on every world switch, and it is read out of a flag
|
||||
# list where a whole-word match is the only thing standing between `svm` and
|
||||
# `svm_lock`.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@test "the svm reader keeps the sub-features an AMD guest was given" {
|
||||
tmp=$(mktemp -d)
|
||||
printf 'Model name: AMD EPYC 7J13 64-Core Processor\n' >"$tmp/lscpu"
|
||||
printf 'Flags: fpu de tsc svm cr8_legacy npt nrip_save vgif umip pku\n' >>"$tmp/lscpu"
|
||||
|
||||
out=$(runner_svm_features <"$tmp/lscpu")
|
||||
|
||||
# vgif is the whole reason for this line: present here, and its absence on an
|
||||
# otherwise identical host is the only difference measured between a runner
|
||||
# whose nested guests boot and one whose do not.
|
||||
[ "$out" = "svm npt nrip_save vgif" ] || {
|
||||
echo "expected 'svm npt nrip_save vgif', got '$out'" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@test "the svm reader reports the same host without vgif as lacking it" {
|
||||
tmp=$(mktemp -d)
|
||||
printf 'Flags: fpu de tsc svm cr8_legacy npt nrip_save umip pku\n' >"$tmp/lscpu"
|
||||
|
||||
out=$(runner_svm_features <"$tmp/lscpu")
|
||||
|
||||
[ "$out" = "svm npt nrip_save" ] || {
|
||||
echo "expected 'svm npt nrip_save', got '$out'" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@test "the svm reader says nothing about an Intel host, whose vnmi is not SVM" {
|
||||
tmp=$(mktemp -d)
|
||||
# vnmi is a flag name under VMX as well as SVM. Ungated, this reader would
|
||||
# print one SVM sub-feature for a machine that has no SVM, which is not a
|
||||
# shorter answer but a wrong one.
|
||||
printf 'Flags: fpu vme de pse tsc msr vmx ept vpid vnmi\n' >"$tmp/lscpu"
|
||||
|
||||
out=$(runner_svm_features <"$tmp/lscpu")
|
||||
|
||||
[ -z "$out" ] || {
|
||||
echo "expected no output for an Intel flag list, got '$out'" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@test "the svm reader does not take svm from svm_lock alone" {
|
||||
tmp=$(mktemp -d)
|
||||
# A whole-word match is what keeps this honest: svm_lock is a sub-feature name
|
||||
# in its own right and its presence does not mean SVM is exposed.
|
||||
printf 'Flags: fpu de tsc svm_lock npt umip\n' >"$tmp/lscpu"
|
||||
|
||||
out=$(runner_svm_features <"$tmp/lscpu")
|
||||
|
||||
[ -z "$out" ] || {
|
||||
echo "expected no output when only svm_lock is present, got '$out'" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@test "the svm reader reads the first Flags line only" {
|
||||
tmp=$(mktemp -d)
|
||||
# lscpu prints one Flags line, but a caller handing this a concatenated table
|
||||
# would otherwise get two machines' features merged into one answer.
|
||||
printf 'Flags: fpu svm npt vgif\n' >"$tmp/lscpu"
|
||||
printf 'Flags: fpu svm npt avic\n' >>"$tmp/lscpu"
|
||||
|
||||
out=$(runner_svm_features <"$tmp/lscpu")
|
||||
|
||||
[ "$out" = "svm npt vgif" ] || {
|
||||
echo "expected only the first Flags line, got '$out'" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The number reader and the scalar guard, driven directly.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user