Ctrl K
ComunidadGuías…

hack/talos-image-cache_test.bats

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bats
# -----------------------------------------------------------------------------
# Unit tests for the in-sandbox Talos image cache manifest split and the
# importer-reachability contract it relies on.
#
# hack/e2e-talos-image-cache.yaml bundles four documents but they are applied in
# two phases: hack/e2e-install-cozystack.bats applies everything EXCEPT the
# CiliumClusterwideNetworkPolicy (its CRD does not exist before Cozystack is
# installed), and hack/e2e-chainsaw/_lib/talos-image-cache.sh applies ONLY that
# policy later, once Cilium is up. If a future document is added to the manifest
# and silently dropped from the pre-Cilium apply, or the Cilium document leaks
# into it (and errors on the missing CRD), the mirror breaks and e2e falls back
# to the flaky public factory. These tests pin that split.
#
# They also pin the load-bearing reachability invariant: the throwaway probe pod
# is a faithful proxy for a real CDI importer only if it carries the exact label
# and namespace the egress policy selects. If either side drifts, the probe can
# pass while real importers stay blocked (a false positive that makes CI worse).
#
# Finally they cover the decision itself, sourced from
# hack/e2e-chainsaw/_lib/talos-image-cache.sh (sourcing it has no cluster side
# effects — it only sets parameter defaults and defines functions). Two pure
# fragments are exercised directly: the strict 206 gate that decides whether
# tenants are pointed at the mirror at all, and the --overrides JSON builder
# whose silent breakage would drop every probe to the public factory. The
# kubectl orchestration around them is driven here too, against a throwaway
# `kubectl` and `timeout` placed on PATH — the decision of which factory a whole
# suite pulls from is worth pinning without waiting for a cluster.
#
# On PATH, not shell functions, and that is not a style choice: the lookup runs
# `timeout -k 5 40 kubectl …` wherever timeout is installed, and an external
# timeout binary execs a real file.
# A `kubectl() { … }` override is invisible to it, so the stub would never be
# reached, and a suite that stubs that way goes quietly vacuous. A stub on PATH
# is also the only way to assert how the real call was spelled, which matters
# for the flags the three-way answer depends on: the stub answers exit 0 with no
# output for an absence no matter what flags it is handed, while a real kubectl
# does that only with --ignore-not-found, so that flag is pinned by inspecting
# the recorded call rather than by the behaviour around it.
#
# Root cause + fix are from cozystack/cozystack#3254 (@lexfrei); this is the port
# to the Chainsaw layout (hack/e2e-chainsaw/_lib/).
#
# CI runs this file under Bats through `make bats-unit-tests`. It also remains
# compatible with the legacy `hack/cozytest.sh` translator, whose awk parser
# recognizes only @test blocks and a bare `}` on its own line. The tests avoid
# `run`, `$status`, setup(), and teardown() for that compatibility path and use
# direct shell assertions. mikefarah yq's document separators are stripped.
#
# Run with: bats hack/talos-image-cache_test.bats
# -----------------------------------------------------------------------------

load test_helper

@test "manifest documents partition into pre-Cilium apply plus the Cilium policy" {
    manifest=hack/e2e-talos-image-cache.yaml
    total=$(yq '.kind' "$manifest" | grep -vc '^---$')
    excluded=$(yq 'select(.kind != "CiliumClusterwideNetworkPolicy") | .kind' "$manifest" | grep -vc '^---$')
    selected=$(yq 'select(.kind == "CiliumClusterwideNetworkPolicy") | .kind' "$manifest" | grep -vc '^---$')
    [ "$total" -eq 4 ]
    [ "$excluded" -eq 3 ]
    [ "$selected" -eq 1 ]
    [ $((excluded + selected)) -eq "$total" ]
}

@test "pre-Cilium apply keeps Service, Deployment, ConfigMap and drops the Cilium policy" {
    manifest=hack/e2e-talos-image-cache.yaml
    kinds=$(yq 'select(.kind != "CiliumClusterwideNetworkPolicy") | .kind' "$manifest" | grep -v '^---$')
    for want in Service Deployment ConfigMap; do
        printf '%s\n' "$kinds" | grep -qx "$want" || { echo "pre-Cilium apply is missing $want" >&2; exit 1; }
    done
    if printf '%s\n' "$kinds" | grep -qx CiliumClusterwideNetworkPolicy; then
        echo "CiliumClusterwideNetworkPolicy leaked into the pre-Cilium apply" >&2
        exit 1
    fi
}

@test "point-of-use apply selects exactly the Cilium policy" {
    manifest=hack/e2e-talos-image-cache.yaml
    kinds=$(yq 'select(.kind == "CiliumClusterwideNetworkPolicy") | .kind' "$manifest" | grep -v '^---$')
    [ "$kinds" = "CiliumClusterwideNetworkPolicy" ]
}

@test "range server records transfer outcome, bytes and duration" {
    script=$(yq 'select(.kind == "ConfigMap") | .data."serve.py"' hack/e2e-talos-image-cache.yaml)

    printf '%s\n' "$script" | python3 -c 'import sys; compile(sys.stdin.read(), "serve.py", "exec")'
    printf '%s\n' "$script" | grep -Fq 'transfer-finished status=%s path=%s range=%s requested=%d sent=%d duration=%.3fs'
    printf '%s\n' "$script" | grep -Fq 'time.monotonic() - started'
}

@test "egress policy selects the importer label and namespace the probe pod uses" {
    manifest=hack/e2e-talos-image-cache.yaml
    helper=hack/e2e-chainsaw/_lib/talos-image-cache.sh
    label=$(yq 'select(.kind == "CiliumClusterwideNetworkPolicy") | .spec.endpointSelector.matchLabels["k8s:cdi.kubevirt.io"]' "$manifest")
    ns=$(yq 'select(.kind == "CiliumClusterwideNetworkPolicy") | .spec.endpointSelector.matchLabels["k8s:io.kubernetes.pod.namespace"]' "$manifest")
    [ "$label" = "importer" ]
    [ "$ns" = "tenant-test" ]
    # The probe pod must carry the same label the policy selects, else it faces a
    # different egress than a real importer and the guarantee breaks.
    grep -q "cdi.kubevirt.io=importer" "$helper" || { echo "probe pod label drifted from the egress selector" >&2; exit 1; }
    grep -q "TALOS_IMAGE_CACHE_PROBE_NS:-tenant-test" "$helper" || { echo "probe namespace drifted from the egress selector" >&2; exit 1; }
}

@test "egress rule targets the mirror pod's own identity" {
    manifest=hack/e2e-talos-image-cache.yaml
    target=$(yq 'select(.kind == "CiliumClusterwideNetworkPolicy") | .spec.egress[0].toEndpoints[0].matchLabels["k8s:app.kubernetes.io/name"]' "$manifest")
    target_ns=$(yq 'select(.kind == "CiliumClusterwideNetworkPolicy") | .spec.egress[0].toEndpoints[0].matchLabels["k8s:io.kubernetes.pod.namespace"]' "$manifest")
    mirror=$(yq 'select(.kind == "Deployment") | .spec.template.metadata.labels["app.kubernetes.io/name"]' "$manifest")
    mirror_ns=$(yq 'select(.kind == "Deployment") | .metadata.namespace' "$manifest")
    [ "$target" = "talos-image-cache" ]
    [ "$target_ns" = "kube-system" ]
    # The allow's destination must equal the mirror Deployment's own pod label and
    # namespace, otherwise the hole is punched toward nothing: moving the mirror
    # would leave these tests green while importers could no longer reach it.
    [ "$target" = "$mirror" ]
    [ "$target_ns" = "$mirror_ns" ]
}

@test "strict 206 gate selects the mirror on a byte-range success" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    if ! _talos_image_cache_probe_succeeded "code=206"; then
        echo "expected a 206 probe result to select the mirror" >&2
        exit 1
    fi
}

@test "strict 206 gate finds the marker inside surrounding probe output" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    out=$(printf 'pod/talos-image-cache-probe created\ncode=206\npod "talos-image-cache-probe" deleted\n')
    if ! _talos_image_cache_probe_succeeded "$out"; then
        echo "expected 206 to be found in multi-line attach output" >&2
        exit 1
    fi
}

@test "strict 206 gate falls back on range-less, unreachable, empty and unrelated output" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    # A plain 200 means the mirror answered without range support; 000 means curl
    # never connected. Both fall back to the public factory, as does output that
    # carried no result marker at all (an attach stream lost with no logs to recover).
    for bad in "code=200" "code=000" "code=404" "" "error: pods not found"; do
        if _talos_image_cache_probe_succeeded "$bad"; then
            echo "expected non-206 output to fall back to the public factory: [$bad]" >&2
            exit 1
        fi
    done
}

_stub_kubectl_dir() {
    # Build a throwaway PATH entry whose `kubectl get deploy ... -o name` fails
    # its first $2 invocations and then answers with $3 on stdout (empty for a
    # genuine absence). $1 is the scratch dir. printf, not a heredoc: cozytest.sh
    # translates the file with awk and would read a @test or a bare `}` inside a
    # heredoc as real syntax.
    mkdir -p "$1/bin"
    printf '%s\n' \
        '#!/bin/sh' \
        'printf "%s\n" "$*" >> "$STUB_CALLS"' \
        'n=$(cat "$STUB_COUNTER")' \
        'n=$((n + 1))' \
        'printf "%s" "$n" > "$STUB_COUNTER"' \
        'if [ "$n" -le "$STUB_FAILS" ]; then' \
        '  echo "error: the server was unable to return a response" >&2' \
        '  exit 1' \
        'fi' \
        'printf "%s" "$STUB_OUT"' \
        > "$1/bin/kubectl"
    chmod +x "$1/bin/kubectl"
    # A transparent `timeout` that records how it was invoked and then runs the
    # command anyway, so a test can pin the wrapper without changing what the
    # wrapped call does. Same idiom as hack/run-kubernetes-serial-console_test.bats.
    # With STUB_TIMEOUT_RC set it instead exits that status without running the
    # command and without printing, which is what a real kill looks like from the
    # caller's side: the wrapper says nothing and the command never reaches the
    # point where it would have.
    printf '%s\n' \
        '#!/bin/sh' \
        'printf "%s\n" "$*" >> "$STUB_TIMEOUT_CALLS"' \
        'if [ -n "$STUB_TIMEOUT_RC" ]; then exit "$STUB_TIMEOUT_RC"; fi' \
        'while [ $# -gt 0 ] && [ "$1" != kubectl ]; do shift; done' \
        'exec "$@"' \
        > "$1/bin/timeout"
    chmod +x "$1/bin/timeout"
    printf '0' > "$1/counter"
    : > "$1/calls"
    : > "$1/timeout.calls"
    export STUB_COUNTER="$1/counter" STUB_CALLS="$1/calls" \
        STUB_TIMEOUT_CALLS="$1/timeout.calls" STUB_FAILS="$2" STUB_OUT="$3" \
        STUB_TIMEOUT_RC=""
    PATH="$1/bin:$PATH"
}

@test "deployment lookup reports present when the mirror answers with a name" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    d=$(mktemp -d)
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 0 'deployment.apps/talos-image-cache'
    state=$(_talos_image_cache_deploy_state)
    rm -rf "$d"
    [ "$state" = present ]
}

@test "deployment lookup reports absent only on an empty answer the apiserver actually gave" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    d=$(mktemp -d)
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    # --ignore-not-found: a real absence is exit 0 with no output. This is the
    # one outcome allowed to turn the whole suite toward the public factory.
    _stub_kubectl_dir "$d" 0 ''
    state=$(_talos_image_cache_deploy_state)
    # The flag has to be pinned by inspection, because the stub gives exit 0 and
    # no output whether or not the call asks for it. Drop it from the real call
    # and kubectl reports a genuine absence as an error instead: `absent` becomes
    # a state production can never reach, while this test keeps passing.
    flagged=$(grep -c -- '--ignore-not-found' "$d/calls" || true)
    rm -rf "$d"
    [ "$state" = absent ]
    [ "$flagged" = 1 ]
}

@test "deployment lookup reports unknown when the apiserver never answers" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    d=$(mktemp -d)
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    # Every attempt refused. Reading this as "absent" is the defect: a blip would
    # send every tenant worker in the suite to the public factory.
    _stub_kubectl_dir "$d" 99 'deployment.apps/talos-image-cache'
    state=$(_talos_image_cache_deploy_state)
    rm -rf "$d"
    [ "$state" = unknown ]
}

@test "deployment lookup rides out a blip and reports what a later attempt found" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    d=$(mktemp -d)
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 1 'deployment.apps/talos-image-cache'
    state=$(_talos_image_cache_deploy_state)
    tries=$(cat "$d/counter")
    rm -rf "$d"
    [ "$state" = present ]
    [ "$tries" = 2 ]
}

@test "every attempt at the lookup carries a request deadline" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    d=$(mktemp -d)
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 99 'deployment.apps/talos-image-cache'
    state=$(_talos_image_cache_deploy_state)
    # kubectl's --request-timeout defaults to 0, so a connection that
    # establishes and then stalls hangs with no deadline, and re-asking would
    # multiply that hang instead of riding out a blip. The flag alone is not a
    # wall-clock bound either — kubectl retries discovery several times over
    # before it gives up — so each attempt needs the wrapper as well. The last
    # count pins the number of attempts, not their duration.
    bounded=$(grep -c -- '--request-timeout=30s' "$d/calls" || true)
    # The wall-clock bound is deliberately above the request deadline beside it:
    # level with it, the kill lands first every time and --request-timeout can
    # never fire, which is how the one silent failure mode stays silent.
    wrapped=$(grep -c '^-k 5 40 kubectl ' "$d/timeout.calls" || true)
    total=$(grep -c . "$d/calls" || true)
    rm -rf "$d"
    [ "$state" = unknown ]
    [ "$total" = 3 ]
    [ "$bounded" = 3 ]
    [ "$wrapped" = 3 ]
}

@test "a present mirror routes the decision into the reachability probe" {
    d=$(mktemp -d)
    _TALOS_IMAGE_FACTORY_DECISION_FILE="$d/decision"
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 0 'deployment.apps/talos-image-cache'
    set +x
    url=$(resolve_talos_image_factory_url 2>"$d/err")
    err=$(cat "$d/err")
    rm -rf "$d"
    # The state token is a contract between this helper and its two readers.
    # Drift on the resolve side is the worst case there is: the present branch
    # would fall through to "not deployed", cache an empty URL, and send every
    # tenant worker in the suite to the public factory — the very harm the
    # three-way answer exists to prevent, with every other test still green.
    [ -z "$url" ]
    printf '%s' "$err" | grep -q 'Available but a tenant-scoped probe' || { echo "a present mirror must reach the probe, not the not-deployed branch: [$err]" >&2; exit 1; }
}

@test "an undecidable lookup warns and is not cached for the rest of the suite" {
    d=$(mktemp -d)
    _TALOS_IMAGE_FACTORY_DECISION_FILE="$d/decision"
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 99 'deployment.apps/talos-image-cache'
    # cozytest.sh runs each test under `set -x`, and xtrace shares the stderr
    # this captures. Off for the call, so the assertions below read what the
    # function said rather than an echo of the commands it ran, and left off
    # afterwards: a test that turns it back on and then fails wedges `bats`
    # instead of reporting, which costs a reader the failure they came for.
    set +x
    url=$(resolve_talos_image_factory_url 2>"$d/err")
    cached=no
    if [ -f "$d/decision" ]; then cached=yes; fi
    err=$(cat "$d/err")
    rm -rf "$d"
    # Falling back for this one caller is fine; pinning that fallback for every
    # later tenant test on the strength of a failed question is not.
    [ -z "$url" ]
    [ "$cached" = no ]
    printf '%s' "$err" | grep -q 'could not determine' || { echo "an undecidable lookup must say so: [$err]" >&2; exit 1; }
    # And it must say why. "could not determine" alone leaves an operator with a
    # new silent failure in place of the old one: refused, Unauthorized and
    # timed-out all read the same until kubectl's own message reaches the log.
    printf '%s' "$err" | grep -q 'the server was unable to return a response' || { echo "the reason the lookup failed must reach the log: [$err]" >&2; exit 1; }
}

@test "a lookup killed by the wall-clock bound still accounts for itself" {
    d=$(mktemp -d)
    _TALOS_IMAGE_FACTORY_DECISION_FILE="$d/decision"
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 0 'deployment.apps/talos-image-cache'
    # The stall this loop exists for, and the one case where nobody speaks:
    # `timeout` prints nothing when it fires, and the kubectl it killed never
    # reached the point where it would have printed either. The sibling test
    # below pins the reason on a kubectl that failed loudly, which every stub
    # does by default -- so without this case "the reason reaches the log" is
    # pinned only where it was never in doubt.
    export STUB_TIMEOUT_RC=124
    set +x
    url=$(resolve_talos_image_factory_url 2>"$d/err")
    err=$(cat "$d/err")
    spoke=$(grep -c . "$d/calls" || true)
    rm -rf "$d"
    [ -z "$url" ]
    # kubectl never ran, so anything the warning points at has to come from the
    # lookup itself. Drop this and the case degrades into the sibling below.
    [ "$spoke" = 0 ]
    printf '%s' "$err" | grep -q 'the last one exited 124' || { echo "a lookup cut off by its own bound must say so: [$err]" >&2; exit 1; }
}

@test "a genuine absence still resolves once and stays cached" {
    d=$(mktemp -d)
    _TALOS_IMAGE_FACTORY_DECISION_FILE="$d/decision"
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 0 ''
    set +x
    url=$(resolve_talos_image_factory_url 2>"$d/err")
    cached=no
    if [ -f "$d/decision" ]; then cached=yes; fi
    err=$(cat "$d/err")
    rm -rf "$d"
    [ -z "$url" ]
    [ "$cached" = yes ]
    printf '%s' "$err" | grep -q 'not deployed' || { echo "a real absence must report itself as such: [$err]" >&2; exit 1; }
}

@test "with timeout off PATH the lookup still asks instead of answering unknown" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    d=$(mktemp -d)
    _TALOS_IMAGE_CACHE_QUERY_DELAY=0
    _stub_kubectl_dir "$d" 0 'deployment.apps/talos-image-cache'
    rm -f "$d/bin/timeout"
    # Everything the kubectl stub itself needs, staged from known directories so
    # the stripped PATH below loses `timeout` and nothing else. Resolved this way
    # rather than through `command -v` for the reason the same idiom gives in
    # hack/run-kubernetes-node-join_test.bats: a name mocked as a shell function
    # answers with the function and no link gets made.
    for c in cat sleep; do
        for p in /bin /usr/bin /usr/local/bin /opt/homebrew/bin; do
            if [ -x "$p/$c" ]; then ln -sf "$p/$c" "$d/bin/$c"; break; fi
        done
    done
    if [ ! -x "$d/bin/cat" ]; then
        echo "could not stage a real cat in the stripped PATH; the check below would be vacuous" >&2
        rm -rf "$d"
        exit 1
    fi
    # Stripped for the call and restored right after: the assertions below need a
    # `grep` this PATH deliberately does not have.
    oldpath=$PATH
    PATH="$d/bin"
    state=$(_talos_image_cache_deploy_state)
    PATH=$oldpath
    wrapped=$(grep -c . "$d/timeout.calls" || true)
    rm -rf "$d"
    # Calling the wrapper anyway when it is not installed makes every attempt
    # exit 127, and three of those read as an apiserver that never answered: a
    # missing local binary would pin the whole suite to the public factory while
    # every message blamed the cluster. Asserting the state rather than the exit
    # is what distinguishes the two — 127 lands on `unknown`, not on an error.
    [ "$state" = present ]
    [ "$wrapped" = 0 ]
}

@test "probe overrides builder emits valid JSON with the image, command and restricted PSA" {
    . hack/e2e-chainsaw/_lib/talos-image-cache.sh
    img=example.invalid/img:tag
    cmd='curl -s -o /dev/null -w code=%{http_code} -r 0-0 http://talos-image-cache.kube-system.svc/x.raw.xz'
    json=$(_talos_image_cache_probe_overrides "$img" "$cmd")
    # Invalid JSON would make every probe fail and silently drop CI back to the
    # public factory, so parse it rather than pattern-match it.
    printf '%s' "$json" | yq -p=json -o=json '.' >/dev/null
    [ "$(printf '%s' "$json" | yq -p=json '.spec.containers[0].name')" = "c" ]
    [ "$(printf '%s' "$json" | yq -p=json '.spec.containers[0].image')" = "$img" ]
    [ "$(printf '%s' "$json" | yq -p=json '.spec.containers[0].command[2]')" = "$cmd" ]
    [ "$(printf '%s' "$json" | yq -p=json '.spec.securityContext.runAsNonRoot')" = "true" ]
    [ "$(printf '%s' "$json" | yq -p=json '.spec.containers[0].securityContext.allowPrivilegeEscalation')" = "false" ]
}