Files
cozystack/hack/select-install.sh
T
Myasnikov Daniil f75153ac0a test(e2e): run the merge-gating lanes on Talos containers
The srv1-srv3 nodes of both lanes that gate a merge -- the same-repo one
in pull-requests.yaml and the fork one in e2e-fork.yaml -- run as Talos
containers instead of QEMU guests, so a tenant worker sits at L2 rather
than L3. Measured in CI on commits where both substrates ran the same
tree: whole-job wall clock down 24-49 minutes, essentially all of it in
Chainsaw, while the two tenant-Kubernetes suites go from 1143-2040s to
466-809s and stop depending on the runner's vgif flag.

The node-join soft-red gate goes with the substrate, which is what it
was for. It existed because nested virtualisation on the shared runners
degraded far enough that a worker registering in minutes elsewhere could
miss any deadline this test could afford; #3513 established that the
discriminator was the host's kvm_amd vgif flag, and removing a nesting
level removes what it acted on. So hack/e2e-node-join-soft-red.sh, the
SOFT-RED-node-join.txt marker and the soft_red job outputs are gone from
all four workflows, and a missed deadline is an ordinary red again. The
::warning annotation survives, because the reason a run went red should
be legible without opening the log, and so does the 124-versus-everything
-else test, because "the workers were slow" and "the wait never ran"
send a reader to different places.

Done by swapping the substrate inside the existing e2e jobs rather than
by promoting a separate container job. Those jobs carry more than a
substrate -- the GitHub App token, the report-overrun guard, the images
list, the SSH breakpoint -- and promoting a job that never had them
would have dropped four features without saying so.

Three properties of container mode drive the design, and each fails
quietly rather than loudly. machine.kernel.modules is a silent no-op --
kernel_module_spec.go returns early on ModeContainer with no error and
no event -- so the host loads openvswitch and zfs before the nodes start
and the workflow step asserts both rather than trusting them; a missing
module surfaces an hour later looking like a CNI or a storage
regression. Docker gives a privileged container its own tmpfs /dev
seeded once at creation, so /dev/kvm works but ZFS zvols created later
never appear and linstor-csi fails ControllerPublishVolume; the compose
file binds the host devtmpfs, which is also why talosctl cluster create
docker can never carry LINSTOR. And there is no ARP VIP, so
192.168.123.11 stands in for the QEMU lane's .10.

The QEMU machinery stays in the tree and keeps running: nightly.yaml and
e2e-tag.yaml still call `make prepare-env`, so DRBD, the replicated
StorageClass, its Immediate binding mode, the tenant StorageClass
propagation that only applies to remotely-accessible classes, and the
Cozystack Talos node image with its extensions all keep nightly and
release-candidate coverage. None of them is exercised per pull request
any more. That is the trade.

Nothing on the PR path builds or downloads a nocloud disk now, so
build-talos drops the talos-nocloud step and the talos-image artifact.
build-talos itself stays: finalize consumes its digest fragment.

One property this does not fix, recorded beside the runner class because
it will be read off a red run eventually: a container node reports the
HOST's CPU and memory as capacity, and hack/e2e-container-up.sh counters
that with kubelet systemReserved. That corrects the scheduler and not
what a pod reads from /proc, so a workload sizing itself from
/proc/cpuinfo or /proc/meminfo is configured for the whole runner.

Also carried here because the same files carry them: the ghcr.io
pull-through mirror removal (see #4007 -- whichever lands first makes the
other a no-op), the authoritative HelmRelease readiness gate, the
backup-credential preflight, the prepull guard against an image-less
container fragment, and folding the two standalone OIDC suites into the
latest-version tenant cluster.

Adapted for release-1.6: this branch has no e2e-fork.yaml, no soft-red
gate, no ghcr.io mirror and no backup e2e, so those hunks are dropped
together with the tests for helpers that exist only on main; e2e-tag.yaml
stays on QEMU. The platform Package generator carries the API endpoint
and nothing else, because the linstor drbd.enabled and kubevirt-cdi
importerResources overrides need chart values this branch does not ship;
the container lane gets e2e-side substitutes for them in later commits.
run-kubernetes.sh takes the storage-class helper, the local-lane pool
baseline, the scoped drain and the OIDC fold, while cleanup keeps its
best-effort `|| true`. The OIDC suite references in select-install.sh and
the chainsaw README go with the suites here rather than in a later commit.

(cherry picked from commit 186547d835)
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-by: LLM
2026-09-24 14:56:35 +05:00

224 lines
9.2 KiB
Bash
Executable File

#!/bin/sh
# Given the E2E app suite(s) about to run, emit the minimal set of packages that
# must be INSTALLED for those suites — the forward dependency closure over the
# PackageSource graph. This is the install-side companion to select-e2e.sh:
# select-e2e.sh picks WHICH Chainsaw suites run (reverse dependency walk);
# select-install.sh picks WHAT must be up for them (forward dependency walk).
#
# Usage:
# hack/select-install.sh <suites> [<sources-dir>]
# hack/select-install.sh --validate [<sources-dir> [<suites-dir>]]
#
# Defaults: sources-dir = packages/core/platform/sources
# suites-dir = hack/e2e-chainsaw
#
# <suites>: whitespace-separated suite names, i.e. the output of select-e2e.sh
# (e.g. "postgres harbor"). "-" reads the list from stdin.
#
# Output (closure mode): space-separated PackageSource names (cozystack.*) that
# the run must enable, seeds + the engine + all transitive dependsOn. Empty
# when <suites> is empty. A suite with no known mapping is a HARD ERROR (fail
# closed) — a silently-skipped suite would install nothing and let the test
# pass vacuously or fail later on a missing CRD.
#
# --validate: check the whole graph AND the suite mapping — every dependsOn
# target resolves to a real PackageSource, there are no dependency cycles, and
# every Chainsaw suite under <suites-dir> resolves via suite_to_source() to a
# real PackageSource. Exit non-zero on any failure. The graph lives
# next to the packages and cannot drift from the suite directories; the
# hand-maintained suite mapping is the only part that can, so it is guarded here.
#
# Unlike select-e2e.sh's reverse walk, the forward walk KEEPS the
# cozystack.cozystack-engine edge: every *-application declares
# `dependsOn: cozystack.cozystack-engine` because the app's *-rd HelmRelease
# needs the engine to register the ApplicationDefinition CRD before it can
# reconcile. For test SELECTION that edge is noise (it would fan every app out
# to every other); for INSTALL it is a genuine prerequisite that must be up.
# Suites backed by a system package (e.g. securitygroup) carry no such edge, so
# the engine is seeded explicitly for any non-empty closure (see below).
set -eu
SOURCES_DIR="packages/core/platform/sources"
SUITES_DIR="hack/e2e-chainsaw"
MODE="closure"
SUITES=""
if [ "${1:-}" = "--validate" ]; then
MODE="validate"
SOURCES_DIR="${2:-$SOURCES_DIR}"
SUITES_DIR="${3:-$SUITES_DIR}"
else
suites_arg="${1?usage: select-install.sh <suites> [sources-dir] | --validate [sources-dir [suites-dir]]}"
SOURCES_DIR="${2:-$SOURCES_DIR}"
if [ "$suites_arg" = "-" ]; then
SUITES="$(cat)"
else
SUITES="$suites_arg"
fi
fi
# suite name -> owning PackageSource. Prints:
# - a "cozystack.*" name the suite's primary install target
# - nothing no mapping is known (caller fails closed)
# Most suites follow the <suite>-application convention; a bare-<suite> fallback
# and the explicit cases cover suites whose source is named differently. Kept in
# lockstep with select-e2e.sh's src_to_suites() and the hack/e2e-chainsaw/ dirs;
# --validate asserts every suite dir still resolves here.
#
# Note: a suite may exercise fixtures or selectors that name other app kinds
# (e.g. securitygroup's SecurityGroup references Postgres/Kubernetes by kind and
# name as selectors — it does not stand those apps up). Only the primary package
# is mapped here; the forward closure covers its own deps, the seeded engine
# covers the aggregated *.cozystack.io APIs, and the full-suite fallback covers
# the rest. Modelling multi-package suites is deferred to the test-minimal work.
suite_to_source() {
case "$1" in
kubernetes-latest|kubernetes-previous)
echo cozystack.kubernetes-application ; return ;;
vminstance) echo cozystack.vm-instance-application ; return ;;
securitygroup) echo cozystack.securitygroup-controller ; return ;;
esac
for cand in "cozystack.$1-application" "cozystack.$1"; do
if echo "$NODES" | grep -Fxq "$cand"; then echo "$cand"; return; fi
done
}
# yq: forward edges — "owner<TAB>dep", one per variant dependsOn entry.
# NOTE: this unions dependsOn across ALL spec.variants[] rather than the single
# variant actually installed (e.g. cozystack.networking has 6 variants; only 5
# declare gateway-api-crds). That OVER-selects, never under-selects, so the
# emitted set is a safe superset of the true minimal set for the chosen variant.
build_forward_deps() {
yq -rN '.metadata.name as $n | .spec.variants[]?.dependsOn[]? | select(. != null and . != "") | $n + "\t" + .' "$SOURCES_DIR"/*.yaml
}
NODES="$(yq -rN '.metadata.name | select(. != null and . != "")' "$SOURCES_DIR"/*.yaml | sort -u)"
FORWARD="$(build_forward_deps | sort -u)"
# forward deps of a single node
deps_of() {
echo "$FORWARD" | awk -v s="$1" -F'\t' '$1==s {print $2}'
}
# all Chainsaw suite names under a suites-dir (dirs holding chainsaw-test.yaml),
# discovered exactly like select-e2e.sh.
discover_suites() {
find "$1" -mindepth 2 -maxdepth 2 -name chainsaw-test.yaml 2>/dev/null \
| sed -e 's,/chainsaw-test\.yaml$,,' -e 's,.*/,,' | sort
}
if [ "$MODE" = "validate" ]; then
rc=0
# 1. Reachability: every dependsOn target must be a real PackageSource.
echo "$FORWARD" | awk -F'\t' '{print $2}' | sort -u | while IFS= read -r dep; do
[ -z "$dep" ] && continue
if ! echo "$NODES" | grep -Fxq "$dep"; then
owners="$(echo "$FORWARD" | awk -v x="$dep" -F'\t' '$2==x {print $1}' | paste -sd ',' -)"
echo "select-install: dangling dependency '$dep' (referenced by: $owners) has no PackageSource" >&2
echo dangling
fi
done | grep -q dangling && rc=1
# 2. Cycles: from every node with outgoing edges, walk forward; if the walk
# returns to its start, the graph has a cycle through it.
owners_list="$(echo "$FORWARD" | awk -F'\t' '{print $1}' | sort -u)"
for start in $owners_list; do
visited=""
frontier="$(deps_of "$start")"
while [ -n "$frontier" ]; do
next=""
for f in $frontier; do
if [ "$f" = "$start" ]; then
echo "select-install: dependency cycle detected involving '$start'" >&2
rc=1
next=""
break
fi
case " $visited " in *" $f "*) continue ;; esac
visited="$visited $f"
next="$next $(deps_of "$f")"
done
frontier="$next"
done
done
# 3. Suite mapping: every Chainsaw suite dir must resolve to a real source.
# The suite universe is auto-discovered while suite_to_source() is
# hand-maintained, so this is the part that actually drifts. A missing
# suites dir is itself a failure — otherwise a moved/misspelled path would
# make discovery yield nothing and silently pass this whole check.
if [ ! -d "$SUITES_DIR" ]; then
echo "select-install: suites dir '$SUITES_DIR' does not exist" >&2
rc=1
else
for suite in $(discover_suites "$SUITES_DIR"); do
src="$(suite_to_source "$suite")"
if [ -z "$src" ]; then
echo "select-install: suite '$suite' has no PackageSource mapping (add it to suite_to_source)" >&2
rc=1
elif ! echo "$NODES" | grep -Fxq "$src"; then
echo "select-install: suite '$suite' maps to '$src', not a PackageSource in $SOURCES_DIR" >&2
rc=1
fi
done
fi
[ "$rc" = 0 ] && echo "select-install: graph OK ($(echo "$NODES" | grep -c .) sources)" >&2
exit "$rc"
fi
# Closure mode: map suites to seed sources, then take the forward closure.
seeds=""
map_rc=0
for suite in $SUITES; do
[ -z "$suite" ] && continue
src="$(suite_to_source "$suite")"
case "$src" in
"")
# Fail closed: a suite that maps to nothing must abort, not silently emit
# an empty set. Collect every bad suite so one run reports them all.
echo "select-install: error: suite '$suite' has no known PackageSource mapping (add it to suite_to_source)" >&2
map_rc=1
continue ;;
esac
if echo "$NODES" | grep -Fxq "$src"; then
case " $seeds " in *" $src "*) ;; *) seeds="$seeds $src" ;; esac
else
echo "select-install: error: suite '$suite' -> '$src' is not a PackageSource in $SOURCES_DIR" >&2
map_rc=1
fi
done
[ "$map_rc" = 0 ] || exit "$map_rc"
[ -z "$seeds" ] && exit 0
# Any non-empty install set needs the engine up: it registers the
# ApplicationDefinition CRD every *-rd HelmRelease reconciles against, and its
# cozystack-api component serves the aggregated *.cozystack.io APIs some suites
# exercise (e.g. securitygroup -> sdn.cozystack.io). *-application sources carry
# a dependsOn edge to it; system-package-backed suites reach it via no edge, so
# seed it explicitly here. Its absence from the graph is a hard error — nothing
# can install without it.
engine="cozystack.cozystack-engine"
if echo "$NODES" | grep -Fxq "$engine"; then
case " $seeds " in *" $engine "*) ;; *) seeds="$seeds $engine" ;; esac
else
echo "select-install: error: required engine PackageSource '$engine' not found in $SOURCES_DIR" >&2
exit 1
fi
all="$seeds"
while :; do
new=""
for s in $all; do
for d in $(deps_of "$s"); do
case " $all $new " in *" $d "*) ;; *) new="$new $d" ;; esac
done
done
[ -z "$new" ] && break
all="$all $new"
done
echo "$all" | tr ' ' '\n' | grep -v '^$' | sort -u | paste -sd ' ' -