mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 18:47:04 +09:00
Four sections of hack/cozyreport.sh read with a wall-clock ceiling and the rest -- nodes, namespaces, events, HelmReleases, packages, packagesources, PVCs, cert-manager, COSI, kamaji, the Talos node reads and every module gate -- read with none. The tarball is written on the collector's last line, so a single read that never returns does not truncate the report, it loses it, on the run where it is the only evidence left. Each read now goes through the shape that fits it: the existing bounded reader for a read whose output is a file, the existing selector for one that feeds a loop, and a new probe for the `if kubectl get deploy ...` gates. A gate is a read like any other, and an unbounded one is worse than the reads it guards, since the module below it never starts. Its cut-off is recorded, because skipping the module leaves the same empty tree as a cluster that never had the component; a gate that answers "not installed" is recorded nowhere, since that is the cluster answering correctly and a marker firing on every install without KubeVirt is one a reader learns to skip. The reads that stay on the bare wrapper say so where they sit; what they share is that the output is not the reader's to write, going into a variable or through a pipeline that transforms it, so there is no moment at which a marker could attach. The header claimed a per-section list of what was bounded, and the tests froze that list plus a per-section count of the unbounded reads. Both are gone. The list had already gone stale, naming four sections while the file had ten, and the counts were an undercount by construction -- they matched a redirect to `$VAR/`, which no gate, selector or command substitution has. One test replaces both: no read below the library guard runs without a ceiling, checked by looking for a counterexample. A ceiling per read is not a ceiling on the report, and the header now says so where it used to imply the opposite. The pod, VM, VMI and services walks carry a count cap and a time budget; the walks over nodes, namespaces, HelmReleases, packages, PVCs and certificates carry neither, so their cost is the cluster's to set. What this removes is the read that never returns. The bounded reader now names the tool it ran and describes what that tool returned. It served kubectl alone until this change gave it talosctl and the four host commands, so a `df` that hit a stale mount left a marker reading "kubectl exited 1 ... not because the object ended" -- two false statements in one line, in an artifact whose premise is that its notes can be believed. The exec reader keeps the remote command's whole message in the listing it belongs to. `kubectl exec` appends its own `command terminated with exit code N` after whatever the command said, so quoting the last line there drops the cause and keeps the trailer, and sending the message away from the file leaves a zero-byte listing -- the "returned nothing, or never ran" question this collector exists to answer. Its cut-off note also stops asserting that the archive is beside it: a stream that got members across is kept and truncated, one killed before its first byte leaves zero bytes and the call site's emptiness check removes it, and the reader runs before that check, so it names both. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
85 lines
3.8 KiB
Bash
85 lines
3.8 KiB
Bash
#!/usr/bin/env bats
|
|
# Regression coverage for host-node Talos diagnostics in hack/cozyreport.sh.
|
|
# The full report needs a cluster; this test sources only the focused helper and
|
|
# replaces talosctl with a shell function that records the exact argument vector.
|
|
|
|
HACK_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")" && pwd)"
|
|
SCRIPT="$HACK_DIR/cozyreport.sh"
|
|
|
|
COZYREPORT_LIB=1
|
|
# shellcheck source=/dev/null
|
|
. "$SCRIPT"
|
|
|
|
# A PATH stub, not a shell function. These reads now go through the bounded
|
|
# reader, which runs them under `timeout` -- an external binary that execs its
|
|
# argument and therefore cannot see a function defined in this shell. A function
|
|
# mock is not merely bypassed, it is bypassed SILENTLY: the real talosctl runs, and
|
|
# what the test then reports is whatever the machine happens to have installed.
|
|
#
|
|
# cozytest.sh's parser ends an @test block at the first bare `}`, so the helper
|
|
# stays at top level rather than nested inside a test.
|
|
talos_stub_dir() {
|
|
_td=$1
|
|
mkdir -p "$_td/bin"
|
|
cat > "$_td/bin/talosctl" <<'STUB'
|
|
#!/bin/sh
|
|
printf '%s\n' "$*" >> "$TALOSCTL_CALLS"
|
|
[ -z "${TALOSCTL_FAIL:-}" ] && exit 0
|
|
echo 'error: rpc error: code = Unavailable desc = connection refused' >&2
|
|
exit 1
|
|
STUB
|
|
chmod +x "$_td/bin/talosctl"
|
|
# And a `timeout` that runs its command rather than timing it, so the assertions
|
|
# do not depend on the host having GNU coreutils.
|
|
printf '#!/bin/sh\n[ "$1" = "-k" ] && shift 2\nshift\nexec "$@"\n' > "$_td/bin/timeout"
|
|
chmod +x "$_td/bin/timeout"
|
|
COZYREPORT_BOUND="timeout -k 5 $COZYREPORT_READ_TIMEOUT"
|
|
}
|
|
|
|
@test "Talos host diagnostics pass an endpoint and use valid dmesg flags" {
|
|
# Cleanup at the end of the body, never a `trap ... EXIT`. An EXIT trap inside
|
|
# an @test replaces the one the bats binary installs for its own bookkeeping, and
|
|
# a test that then fails prints no TAP line at all -- it does not appear as
|
|
# `not ok`, it disappears, so a reader grepping the output sees a green suite.
|
|
# See docs/agents/e2e-testing.md. Both runners set `set -e`, so on a failure the
|
|
# cleanup line is never reached and the directory survives for inspection, which
|
|
# is what you want from a test that just failed.
|
|
tmp=$(mktemp -d)
|
|
talos_stub_dir "$tmp"
|
|
calls="$tmp/talosctl.calls"
|
|
|
|
TALOSCTL_CALLS="$calls" PATH="$tmp/bin:$PATH" \
|
|
cozyreport_collect_talos_node /workspace/talosconfig 192.0.2.11 "$tmp"
|
|
|
|
[ "$(sed -n '1p' "$calls")" = "--talosconfig /workspace/talosconfig -e 192.0.2.11 -n 192.0.2.11 dmesg" ]
|
|
[ "$(sed -n '2p' "$calls")" = "--talosconfig /workspace/talosconfig -e 192.0.2.11 -n 192.0.2.11 logs kubelet --tail=500" ]
|
|
[ "$(sed -n '3p' "$calls")" = "--talosconfig /workspace/talosconfig -e 192.0.2.11 -n 192.0.2.11 logs containerd --tail=500" ]
|
|
case "$(sed -n '1p' "$calls")" in *--tail*)
|
|
echo "dmesg must not receive the boolean --tail flag" >&2
|
|
false
|
|
esac
|
|
|
|
rm -rf "$tmp"
|
|
}
|
|
|
|
@test "a Talos node that refuses the connection leaves the reason in its files" {
|
|
tmp=$(mktemp -d)
|
|
talos_stub_dir "$tmp"
|
|
calls="$tmp/talosctl.calls"
|
|
|
|
TALOSCTL_FAIL=1 TALOSCTL_CALLS="$calls" PATH="$tmp/bin:$PATH" \
|
|
cozyreport_collect_talos_node /workspace/talosconfig 192.0.2.11 "$tmp"
|
|
|
|
# The reads are best-effort and must not abort the report, but "did not abort"
|
|
# and "produced nothing" are different outcomes and only one of them is about
|
|
# the node. Each read merges stderr into its own file, so a node that refused
|
|
# the connection says so where a reader looking for its dmesg will find it,
|
|
# rather than leaving three empty files that read like a quiet node.
|
|
for f in dmesg.txt kubelet.log containerd.log; do
|
|
t="$tmp/talos-192.0.2.11-$f"
|
|
[ -s "$t" ] || { echo "FAIL: $t is empty on a refused connection"; false; }
|
|
grep -q 'connection refused' "$t" || { echo "FAIL: $t does not carry the reason"; cat "$t"; false; }
|
|
done
|
|
rm -rf "$tmp"
|
|
}
|