mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
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. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
150 lines
7.4 KiB
Bash
150 lines
7.4 KiB
Bash
#!/usr/bin/env bats
|
|
# -----------------------------------------------------------------------------
|
|
# Cozystack E2E provisioning — CONTAINER lane
|
|
#
|
|
# The counterpart to hack/e2e-prepare-cluster.bats. That file boots three QEMU
|
|
# guests; this one takes over after hack/e2e-container-up.sh has started three
|
|
# Talos containers on the runner's docker daemon and attached this sandbox to
|
|
# their network. Everything from bootstrap onward is deliberately the same shape
|
|
# as the QEMU lane so the two stay comparable, and both hand off an identical
|
|
# cluster to hack/e2e-install-cozystack.bats.
|
|
#
|
|
# Why the split: containers cannot nest inside the sandbox the way QEMU guests
|
|
# do, so the nodes must be siblings on the host daemon for hack/e2e-compose.yaml's
|
|
# /dev and /run/udev binds to resolve against the real host devtmpfs. Only the
|
|
# host-side half needs a docker socket; this half needs only the network.
|
|
#
|
|
# Addressing, CPU and memory match the QEMU lane exactly, so the MetalLB
|
|
# 192.168.123.200-250 pool and hack/e2e-post-install-prep.sh are unchanged.
|
|
# The one deliberate difference is the API endpoint: container mode has no ARP
|
|
# VIP, so .11 stands in for the QEMU lane's 192.168.123.10.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
HACK_DIR="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")" && pwd)"
|
|
E2E_CONTAINER_UP_LIB=true . "$HACK_DIR/e2e-container-up.sh"
|
|
|
|
@test "Talos API is reachable on all three container nodes" {
|
|
# e2e-container-up.sh has already started them; this is the handoff check, and
|
|
# it is a wait rather than a probe because container start is asynchronous.
|
|
if ! timeout 180 sh -ec 'until nc -nz 192.168.123.11 50000 && nc -nz 192.168.123.12 50000 && nc -nz 192.168.123.13 50000; do sleep 1; done'; then
|
|
echo "Talos API never came up on 192.168.123.11-13:50000." >&2
|
|
echo "The sandbox reaches the nodes over the compose network it was attached to;" >&2
|
|
echo "if this times out, check that hack/e2e-container-up.sh ran and that the" >&2
|
|
echo "sandbox is on the cozy-e2e_talos network." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "Host kernel modules are usable from inside the nodes" {
|
|
# THE trap of this lane. machine.kernel.modules is a silent no-op in container
|
|
# mode -- kernel_module_spec.go returns early on ModeContainer with no error
|
|
# and no event -- so a lane that carried the QEMU patch over would look like it
|
|
# worked and fail much later as a CNI or storage error. e2e-container-up.sh
|
|
# loads these on the host; this asserts the nodes actually see them, which is
|
|
# the property that matters and is not implied by the host check alone.
|
|
for mod in openvswitch zfs; do
|
|
if ! talosctl read /proc/modules -n 192.168.123.11 -e 192.168.123.11 | grep -q "^${mod} "; then
|
|
echo "Kernel module '${mod}' is not visible from srv1." >&2
|
|
echo "Container nodes share the host kernel and Talos cannot load modules in" >&2
|
|
echo "container mode, so this must be loaded on the host before they start." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
@test "/dev/kvm is present inside the nodes" {
|
|
# KubeVirt's device plugin needs it, and its presence is what makes the tenant
|
|
# workers run under real KVM rather than TCG emulation.
|
|
if ! talosctl ls /dev/kvm -n 192.168.123.11 -e 192.168.123.11 >/dev/null 2>&1; then
|
|
echo "/dev/kvm is missing inside srv1 — KubeVirt cannot advertise devices.kubevirt.io/kvm." >&2
|
|
echo "hack/e2e-compose.yaml binds the host /dev; check the host exposes /dev/kvm." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "Bootstrap Talos cluster" {
|
|
# Retry for up to 120s: talosctl bootstrap refuses with "time is not in sync
|
|
# yet" until NTP converges on a freshly started node. This is the same
|
|
# allowance the QEMU lane makes, and it is a genuine infrastructure wait, not
|
|
# a retry around product logic.
|
|
#
|
|
# If it never converges, the usual cause is that the nodes cannot reach the
|
|
# internet at all rather than slow NTP -- most often because docker sets
|
|
# `-P FORWARD DROP` and the compose bridge's traffic is being dropped.
|
|
if ! timeout 120 sh -ec 'until talosctl bootstrap -n 192.168.123.11 -e 192.168.123.11; do sleep 2; done'; then
|
|
echo "talosctl bootstrap never succeeded." >&2
|
|
echo "If the error was 'time is not in sync yet', the nodes likely have no egress:" >&2
|
|
echo "check 'iptables -S FORWARD' on the host for a DROP policy over the compose bridge." >&2
|
|
talosctl dmesg -n 192.168.123.11 -e 192.168.123.11 || true
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "Wait until etcd is healthy" {
|
|
# Same 5m budget and the same reason as the QEMU lane: a 3-node etcd converges
|
|
# through Talos's serialized learner promotion, so the third member is only
|
|
# promoted once the second has caught up.
|
|
if ! timeout 300 sh -ec 'until talosctl etcd members -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.11 >/dev/null 2>&1; do sleep 1; done'; then
|
|
talosctl dmesg -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.11 || true
|
|
exit 1
|
|
fi
|
|
timeout 60 sh -ec 'while talosctl etcd members -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.11 2>&1 | grep -q "rpc error"; do sleep 1; done'
|
|
}
|
|
|
|
@test "Retrieve kubeconfig and register all three nodes" {
|
|
rm -f kubeconfig
|
|
# No ARP VIP in container mode, so the endpoint is a node IP rather than the
|
|
# QEMU lane's 192.168.123.10.
|
|
talosctl kubeconfig kubeconfig -e 192.168.123.11 -n 192.168.123.11
|
|
|
|
if ! timeout 120 sh -ec 'until [ $(kubectl get node --no-headers | wc -l) -eq 3 ]; do sleep 1; done'; then
|
|
echo "Fewer than three nodes registered:" >&2
|
|
kubectl get nodes -o wide >&2 || true
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "Node allocatable matches the container limits" {
|
|
# Container mode exposes host-wide capacity through /proc, so without the
|
|
# systemReserved values generated by e2e-container-up.sh every one of these
|
|
# nodes advertises the runner's full 32 CPU / ~125 GiB. That lets the
|
|
# scheduler pack far beyond each 8 CPU / 24 GiB cgroup and turns later OOMs
|
|
# into apparent workload flakes. Check the scheduler-visible result, not just
|
|
# the machine-config input.
|
|
local node cpu cpu_m memory memory_kib
|
|
local node_cpu_limit="${COZY_E2E_NODE_CPUS:-8}"
|
|
local node_memory_limit_mib="${COZY_E2E_NODE_MEMORY_MIB:-24576}"
|
|
local max_cpu_m=$(( node_cpu_limit * 1000 ))
|
|
local min_cpu_m=$(( max_cpu_m - 1000 ))
|
|
local max_memory_kib=$(( node_memory_limit_mib * 1024 ))
|
|
local min_memory_kib=$(( max_memory_kib - 1024 * 1024 ))
|
|
|
|
for node in srv1 srv2 srv3; do
|
|
cpu=$(kubectl get node "$node" -o jsonpath='{.status.allocatable.cpu}')
|
|
case "$cpu" in
|
|
*m) cpu_m=${cpu%m} ;;
|
|
*[!0-9]* | '')
|
|
echo "node $node reported an unreadable allocatable CPU quantity: '$cpu'" >&2
|
|
return 1
|
|
;;
|
|
*) cpu_m=$(( cpu * 1000 )) ;;
|
|
esac
|
|
|
|
memory=$(kubectl get node "$node" -o jsonpath='{.status.allocatable.memory}')
|
|
if ! memory_kib=$(kubernetes_memory_to_kib "$memory"); then
|
|
echo "node $node reported an unreadable allocatable memory quantity: '$memory'" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [ "$cpu_m" -lt "$min_cpu_m" ] || [ "$cpu_m" -gt "$max_cpu_m" ]; then
|
|
echo "node $node allocatable CPU $cpu is outside ${min_cpu_m}-${max_cpu_m}m" >&2
|
|
return 1
|
|
fi
|
|
if [ "$memory_kib" -lt "$min_memory_kib" ] || [ "$memory_kib" -gt "$max_memory_kib" ]; then
|
|
echo "node $node allocatable memory $memory is outside ${min_memory_kib}-${max_memory_kib}Ki" >&2
|
|
return 1
|
|
fi
|
|
echo "$node allocatable: ${cpu}/${memory}"
|
|
done
|
|
}
|