mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-14 00:23:54 +09:00
The harbor, openbao and qdrant app charts deploy their workload through a nested HelmRelease named `<release>-system`, and pass `fullnameOverride: <release>` down to it. The inner chart therefore stamps app.kubernetes.io/instance from its own release name, with the suffix, while object names come from the override, without it. The catch blocks selected on the outer release name, so podLogs matched no pods and every failed run collected an empty result. A pod name says nothing about its instance label; the two come from different sources on purpose. In qdrant the unsuffixed value is therefore not retired but kept as a collector of its own: the parent chart's PVC-cleanup Job labels its pod template with the parent release name, and that suite deletes the parent inside a `try:`, so the Job is in scope while the test still runs. The comment there is explicit about how narrow that window is -- the hook always exits 0, so Helm calls it succeeded and `hook-succeeded` takes the pod away; what is left is an uninstall that hangs with Helm still waiting on it. Harbor renders the same hook and does not get the same collector. It has no `delete` op, so its parent release is uninstalled only in Chainsaw's cleanup phase, which no catch can observe: catch is a defer inside the step, while the cleaner runs from t.Cleanup, after the test function returns. That collector would be the same permanently-empty capture this commit removes. A comment records the asymmetry so the next reader does not restore it for symmetry. openbao renders no such hook at all. Each workload value is now also pinned on a resource in an assert the suite already runs, so the chart renaming a label fails a step instead of quietly emptying a capture. The comments state what that does and does not cover: it does not catch a wrong value typed into a selector, and it catches nothing when the run fails before the assert. Harbor's database was reached by no collector at all. It is selected on `cnpg.io/cluster`, CNPG's own primary label, rather than on the instance label CNPG merely derives from the Cluster name -- the primary label has nothing to drift from, and it is what the postgres suite and the WorkloadMonitors already use. Each lookup keeps stderr apart from the value it captures. kubectl exits 0 while logging a klog line about an API group it could not reach, and an aggregated APIService in that state is what a failed run usually looks like; folded into the value, that line makes the "nothing found" branch unreachable and prints as though it were a result. Telling those outcomes apart is one helper in hack/e2e-chainsaw/_lib/ rather than a copy per call site, separated from the kubectl call so it can be pinned without a cluster, the way etcd-probe.sh already is. Both the census and the qdrant cleanup listing report through it, differing only in wording, so the discrimination is implemented and tested once. The tests cover each outcome for both wordings, including the zero exit with a klog line on stderr -- the case a folded stream turned into a row. A collector that matches nothing cannot say so: podLogs is silent, and `kubectl get -l` exits zero on an empty match. Each test-wide catch prints the instance label of every pod in the namespace, and the qdrant cleanup catch reports its three outcomes apart from a single call -- printing what that call returned rather than running it again, which could put a healthy listing under a failure heading. That census runs on a degraded cluster, where kubectl otherwise retries discovery against an unreachable apiserver until something kills the script, so every lookup carries `--request-timeout`. Measured against an unreachable apiserver, a bounded call costs about five times the flag, since kubectl retries discovery five times before it gives up. Each script then carries an explicit op timeout sized above its own calls -- 2m for the single-call censuses, 5m for the qdrant cleanup listing, which makes three. An inner budget is what should fire: a SIGKILL at the op boundary yields nothing, where a kubectl that gives up leaves an error to print. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
110 lines
4.6 KiB
Bash
110 lines
4.6 KiB
Bash
# shellcheck shell=sh
|
|
# Sourced by the chainsaw app Tests after `cd` to the repo root. Provides:
|
|
# lookup_report — tell a diagnostic lookup's three outcomes apart,
|
|
# with the wording left to the caller; no cluster
|
|
# pod_label_census_report — lookup_report with the census wording
|
|
# pod_label_census — print app.kubernetes.io/instance for every pod in
|
|
# a namespace, keeping the outcomes apart
|
|
# Sourcing has no side effects: it defines three functions and nothing else.
|
|
# None uses `local`, which is not POSIX and so unavailable to the `sh` that
|
|
# Chainsaw runs these steps under; the `_plc_` prefix is what keeps the
|
|
# variables from colliding with a caller's names.
|
|
#
|
|
# Why this exists. A `podLogs` collector selects on a label, and it cannot
|
|
# report that it matched nothing — an empty capture and a selector that has
|
|
# gone stale look identical in the artifact. This prints the values that
|
|
# actually exist, so a reader can see whether the selector above it can match.
|
|
# Deliberately no selector is repeated here: a copy would only prove itself
|
|
# right, and would keep matching after the collector's own selector drifted.
|
|
#
|
|
# The three outcomes are kept apart because each means something different and
|
|
# two of them are easy to misread as the third:
|
|
#
|
|
# - the lookup did not run — NOT the same as "no pods"; saying so
|
|
# would report an unreachable apiserver
|
|
# as a healthy empty namespace
|
|
# - it ran and matched nothing — a real, informative answer
|
|
# - it ran and returned rows — the census proper
|
|
#
|
|
# stderr is kept apart from the captured value rather than folded in with
|
|
# `2>&1`, and that is the load-bearing part. kubectl exits 0 while logging a
|
|
# klog line about an API group it could not reach, and an aggregated APIService
|
|
# in that state is what a failed run usually looks like (see
|
|
# docs/agents/e2e-testing.md). Folded into the value, that line makes the
|
|
# "no pods" branch unreachable and prints under the census heading as though it
|
|
# were a pod. Separated, it becomes what it is: a caveat that the census may be
|
|
# partial, printed alongside the answer rather than instead of it.
|
|
#
|
|
# `--request-timeout` bounds each attempt because on a degraded cluster kubectl
|
|
# otherwise retries discovery until something kills the script. Measured
|
|
# against an unreachable apiserver, a bounded call costs about five times the
|
|
# flag, since kubectl retries discovery five times before it gives up; the
|
|
# caller's op timeout is sized above that.
|
|
#
|
|
# The report half is separated from the kubectl call precisely so it can be
|
|
# tested without a cluster — see hack/pod-label-census_test.bats, matching how
|
|
# hack/e2e-chainsaw/_lib/etcd-probe.sh is covered.
|
|
|
|
# lookup_report RC STDOUT STDERR FAILED_MSG EMPTY_MSG ROWS_HEADING
|
|
#
|
|
# The outcome logic, with the wording left to the caller. Every diagnostic
|
|
# lookup in a `catch` has the same three answers to keep apart and the same two
|
|
# ways of confusing them, so they share one implementation and one set of tests
|
|
# rather than a copy per call site.
|
|
#
|
|
# Always returns 0: this runs inside a `catch`, where a non-zero exit would mask
|
|
# the test's own error.
|
|
lookup_report() {
|
|
_plc_rc=$1
|
|
_plc_out=$2
|
|
_plc_err=$3
|
|
_plc_failed=$4
|
|
_plc_empty=$5
|
|
_plc_heading=$6
|
|
|
|
if [ "$_plc_rc" -ne 0 ]; then
|
|
echo "$_plc_failed"
|
|
[ -n "$_plc_err" ] && echo "$_plc_err"
|
|
return 0
|
|
fi
|
|
|
|
if [ -z "$_plc_out" ]; then
|
|
echo "$_plc_empty"
|
|
else
|
|
echo "$_plc_heading"
|
|
echo "$_plc_out"
|
|
fi
|
|
|
|
if [ -n "$_plc_err" ]; then
|
|
echo "the lookup also logged this, so the result above may be partial:"
|
|
echo "$_plc_err"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# pod_label_census_report RC STDOUT STDERR NAMESPACE
|
|
pod_label_census_report() {
|
|
lookup_report "$1" "$2" "$3" \
|
|
"pod label census FAILED (the lookup did not run; this is not 'no pods'):" \
|
|
"pod label census: no pods in $4" \
|
|
"pod label census, app.kubernetes.io/instance per pod:"
|
|
}
|
|
|
|
# pod_label_census NAMESPACE
|
|
pod_label_census() {
|
|
_plc_ns=$1
|
|
_plc_errfile=$(mktemp)
|
|
|
|
if _plc_stdout=$(kubectl -n "$_plc_ns" --request-timeout=15s get pods \
|
|
-o 'custom-columns=POD:.metadata.name,INSTANCE:.metadata.labels.app\.kubernetes\.io/instance' \
|
|
--no-headers 2>"$_plc_errfile"); then
|
|
_plc_status=0
|
|
else
|
|
_plc_status=1
|
|
fi
|
|
|
|
pod_label_census_report "$_plc_status" "$_plc_stdout" "$(cat "$_plc_errfile")" "$_plc_ns"
|
|
rm -f "$_plc_errfile"
|
|
return 0
|
|
}
|