Ctrl K
ComunidadGuías…

hack/run-kubernetes-talos-diagnostics_test.bats

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

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bats
# Regression coverage for tenant-worker Talos diagnostics in
# hack/e2e-chainsaw/_lib/run-kubernetes.sh. cozytest.sh ends an @test block at
# the first bare closing brace, so command mocks stay at top level.
#
# Scratch directories are removed at the end of the test body, never from a
# `trap ... EXIT`. Such a trap replaces the one the bats binary installs for its
# own bookkeeping, and a test that then fails prints no TAP line at all -- not
# `not ok`, nothing -- so a reader grepping the output for failures sees a green
# suite while the test silently did not finish. Both runners set -e, so on
# failure the cleanup below is unreachable and the directory survives for
# inspection, which is what a failed test wants anyway. See
# docs/agents/e2e-testing.md and hack/bats-no-exit-trap.bats.

load test_helper

kubectl_calls=/dev/null
kubectl_manifest=/dev/null
kubectl_exec_rc=0
kubectl_vmi_json=
talosctl_calls=/dev/null
timeout_calls=/dev/null
timeout_rc=0
timeout_fail_node_ip=
# Per-read failure, which the two knobs above cannot express: one is global and
# the other selects a whole node. The marker below is decided by how many of a
# worker's four reads answered, so every interesting case is a mix -- and with
# only all-or-nothing knobs the boundary between them cannot be reached at all,
# which is how it went untested while a test named for it passed.
#
# Space-separated tokens matched against the composed command; each of the four
# reads carries one the others do not (dmesg, kubelet, links, services).
timeout_fail_commands=

kubectl() {
  printf '%s\n' "$*" >>"${kubectl_calls}"

  if [ "${1:-}" = apply ]; then
    cat >"${kubectl_manifest}"
    return 0
  fi

  if [ "${1:-}" = -n ] && [ "${2:-}" = tenant-test ] \
    && [ "${3:-}" = get ] && [ "${4:-}" = secret ]; then
    if [ "${7:-}" = name ]; then
      printf 'secret/%s\n' "${5}"
      return 0
    fi
    case "${7:-}" in
      *tls.key*) printf '%s\n' 'mock-client-key' ;;
      *) printf '%s\n' 'mock-certificate' ;;
    esac
    return 0
  fi

  if [ "${1:-}" = -n ] && [ "${2:-}" = tenant-test ] \
    && [ "${3:-}" = get ] \
    && [ "${4:-}" = virtualmachineinstances.kubevirt.io ] \
    && [ "${8:-}" = json ] && [ -n "${kubectl_vmi_json}" ]; then
    cat "${kubectl_vmi_json}"
    return 0
  fi

  if [ "${3:-}" = exec ] && [ "${4:-}" = deploy/linstor-controller ]; then
    printf '%s\n' '104857600:satellite-a'
    return 0
  fi

  if [ "${3:-}" = exec ]; then
    printf 'mock capture for %s\n' "$*"
    return "${kubectl_exec_rc}"
  fi

  return 0
}

talosctl() {
  printf '%s\n' "$*" >>"${talosctl_calls}"
  if [ "${1:-}" = --talosconfig ] && [ "${3:-}" = config ] \
    && [ "${4:-}" = add ]; then
    printf '%s\n' 'mock-talosconfig' >"${2}"
  fi
}

timeout() {
  local command_rc=0
  printf '%s\n' "$*" >>"${timeout_calls}"
  [ "${1:-}" = -k ] || return 97
  shift 3
  "$@" || command_rc=$?
  if [ -n "${timeout_fail_node_ip}" ]; then
    case " $* " in
      *" -e ${timeout_fail_node_ip} "*) return 124 ;;
    esac
  fi
  local _tok
  for _tok in ${timeout_fail_commands}; do
    case " $* " in
      *" ${_tok}"*) return 1 ;;
    esac
  done
  [ "${timeout_rc}" -eq 0 ] || return "${timeout_rc}"
  return "${command_rc}"
}

assert_file_contains() {
  local needle="$1"
  local file="$2"

  case "$(cat "${file}")" in
    *"${needle}"*) return 0 ;;
  esac
  printf 'expected %s to contain: %s\n' "${file}" "${needle}" >&2
  return 1
}

assert_file_lacks_pattern() {
  local pattern="$1"
  local file="$2"

  if awk -v pattern="${pattern}" '$0 ~ pattern { found = 1 } END { exit found ? 0 : 1 }' "${file}"; then
    printf 'expected %s not to match: %s\n' "${file}" "${pattern}" >&2
    return 1
  fi
}

@test "worker VMI rows retain names whose default interface has no IP" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  rows=$(printf '%s\n' '{"items":[{"metadata":{"name":"worker-a"},"status":{"interfaces":[{"name":"default","ipAddress":"10.244.1.92"}]}},{"metadata":{"name":"worker-b"},"status":{"interfaces":[{"name":"other","ipAddress":"192.0.2.1"}]}}]}' | cozy_tenant_worker_vmi_rows)
  [ "${rows}" = "$(printf 'worker-a|10.244.1.92\nworker-b|')" ]
}

@test "reader Certificate is short-lived and never embeds credential data" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  kubectl_manifest="$tmp/certificate.yaml"

  cozy_apply_tenant_talos_reader_certificate test-latest-version

  assert_file_contains 'name: kubernetes-test-latest-version-e2e-talos-reader' "$kubectl_manifest"
  assert_file_contains 'duration: 1h' "$kubectl_manifest"
  assert_file_contains 'commonName: kubernetes-test-latest-version-e2e-talos-reader' "$kubectl_manifest"
  assert_file_contains '- os:reader' "$kubectl_manifest"
  assert_file_contains 'name: kubernetes-test-latest-version-talos-ca' "$kubectl_manifest"
  assert_file_contains 'cozystack-e2e.io/tenant-talos-diagnostics: "test-latest-version"' "$kubectl_manifest"
  assert_file_lacks_pattern '^[[:space:]]*(tls[.]crt|tls[.]key|data):' "$kubectl_manifest"
  rm -rf "$tmp"
}

@test "talosconfig uses the tenant CA and the issued reader key" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  kubectl_manifest="$tmp/certificate.yaml"
  talosctl_calls="$tmp/talosctl.calls"

  cozy_prepare_tenant_talosconfig test-latest-version "$tmp"

  [ "$(sed -n '1p' "$kubectl_calls")" = '-n tenant-test delete certificate kubernetes-test-latest-version-e2e-talos-reader --ignore-not-found --wait=true --timeout=10s' ]
  [ "$(sed -n '2p' "$kubectl_calls")" = '-n tenant-test delete secret kubernetes-test-latest-version-e2e-talos-reader --ignore-not-found --wait=true --timeout=10s' ]
  assert_file_contains 'wait -n tenant-test certificate kubernetes-test-latest-version-e2e-talos-reader --for=condition=Ready --timeout=30s' "$kubectl_calls"
  assert_file_contains '-n tenant-test get secret kubernetes-test-latest-version-talos-ca -o go-template={{index .data "tls.crt" | base64decode}}' "$kubectl_calls"
  assert_file_contains '-n tenant-test get secret kubernetes-test-latest-version-e2e-talos-reader -o go-template={{index .data "tls.key" | base64decode}}' "$kubectl_calls"
  assert_file_contains "--talosconfig $tmp/talosconfig config add kubernetes-test-latest-version --ca $tmp/ca.crt --crt $tmp/client.crt --key $tmp/client.key" "$talosctl_calls"
  assert_file_contains "--talosconfig $tmp/talosconfig config context kubernetes-test-latest-version" "$talosctl_calls"
  case "$(LC_ALL=C ls -ld "$tmp/talosconfig")" in
    -rw-------*) ;;
    *) echo "talosconfig permissions are not 0600" >&2; return 1 ;;
  esac
  rm -rf "$tmp"
}

@test "diagnostics Pod has no API token or Secret volume" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  kubectl_manifest="$tmp/pod.yaml"

  cozy_apply_tenant_talos_diagnostics_pod test-latest-version

  assert_file_contains 'automountServiceAccountToken: false' "$kubectl_manifest"
  assert_file_contains 'runAsNonRoot: true' "$kubectl_manifest"
  assert_file_contains 'readOnlyRootFilesystem: true' "$kubectl_manifest"
  assert_file_contains 'drop:' "$kubectl_manifest"
  assert_file_contains '- ALL' "$kubectl_manifest"
  assert_file_contains 'image: docker.io/alpine/k8s:1.36.2@sha256:44ef4942e171939b9c665a4a84beb80e2dcdb9a24330d4651cfdfd2e9deecc47' "$kubectl_manifest"
  assert_file_contains "'docker.io/alpine/k8s:1.36.2@sha256:44ef4942e171939b9c665a4a84beb80e2dcdb9a24330d4651cfdfd2e9deecc47'" hack/e2e-install-cozystack.bats
  assert_file_lacks_pattern 'secret:|secretName:' "$kubectl_manifest"
  rm -rf "$tmp"
}

@test "Kubernetes Chainsaw operations leave room for failure diagnostics" {
  [ "$(yq 'select(.metadata.name == "kubernetes-latest").spec.steps[0].try[0].script.timeout' hack/e2e-chainsaw/kubernetes-latest/chainsaw-test.yaml)" = 67m ]
  [ "$(yq 'select(.metadata.name == "kubernetes-previous").spec.steps[0].try[0].script.timeout' hack/e2e-chainsaw/kubernetes-previous/chainsaw-test.yaml)" = 67m ]
}

@test "orchestrator skips credentials and helper Pod when every VMI lacks an IP" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  kubectl_manifest="$tmp/manifest.yaml"
  kubectl_vmi_json="$tmp/vmis.json"
  talosctl_calls="$tmp/talosctl.calls"
  COZY_REPORT_DIR="$tmp/report"
  COZY_SNAPSHOT_NAME=diagnostic-no-ip
  printf '%s\n' '{"items":[{"metadata":{"name":"worker-a"},"status":{"interfaces":[{"name":"default"}]}},{"metadata":{"name":"worker-b"},"status":{}}]}' >"$kubectl_vmi_json"

  capture_rc=0
  cozy_capture_tenant_talos test-latest-version || capture_rc=$?

  [ "$capture_rc" -eq 1 ]
  assert_file_lacks_pattern '(^|[[:space:]])apply([[:space:]]|$)' "$kubectl_calls"
  assert_file_lacks_pattern 'delete (certificate|secret|pod)|wait (certificate|pod)|exec' "$kubectl_calls"
  [ ! -s "$talosctl_calls" ]
  assert_file_contains 'default VMI interface has no reported IP address' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/worker-a/capture-error.log"
  assert_file_contains 'default VMI interface has no reported IP address' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/worker-b/capture-error.log"
  assert_file_contains 'no tenant worker VMI has a reported IP for Talos diagnostics' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/setup-error.log"
  rm -rf "$tmp"
}

@test "node capture uses the VMI IP for endpoint and node with bounded commands" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"

  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.92 "$tmp/node"

  [ -s "$tmp/node/dmesg.log" ]
  [ -s "$tmp/node/kubelet.log" ]
  [ "$(sed -n '1p' "$kubectl_calls")" = '-n tenant-test exec diagnostics-pod -c diagnostics -- /tmp/talosctl --talosconfig /tmp/talosconfig -e 10.244.1.92 -n 10.244.1.92 dmesg' ]
  [ "$(sed -n '2p' "$kubectl_calls")" = '-n tenant-test exec diagnostics-pod -c diagnostics -- /tmp/talosctl --talosconfig /tmp/talosconfig -e 10.244.1.92 -n 10.244.1.92 logs kubelet --tail=500' ]
  [ "$(sed -n '1p' "$timeout_calls")" = '-k 5 20 kubectl -n tenant-test exec diagnostics-pod -c diagnostics -- /tmp/talosctl --talosconfig /tmp/talosconfig -e 10.244.1.92 -n 10.244.1.92 dmesg' ]
  [ "$(sed -n '2p' "$timeout_calls")" = '-k 5 20 kubectl -n tenant-test exec diagnostics-pod -c diagnostics -- /tmp/talosctl --talosconfig /tmp/talosconfig -e 10.244.1.92 -n 10.244.1.92 logs kubelet --tail=500' ]
  case "$(sed -n '1p' "$kubectl_calls")" in
    *--tail*) echo "dmesg must not receive a --tail flag" >&2; exit 1 ;;
  esac
  assert_file_contains '[capture exit code: 0]' "$tmp/node/dmesg.log"
  assert_file_contains '[capture exit code: 0]' "$tmp/node/kubelet.log"
  rm -rf "$tmp"
}

@test "the service state machine is captured, so a kubelet that never ran is legible" {
  # The log this collector took before was not the whole answer, and on the
  # dominant shape of this failure it is not an answer at all. Talos creates a
  # service's log buffer when its runner opens the log writer, as it starts the
  # process, so a kubelet held in Waiting -- for its volumes, for time sync, or
  # for the container runtime to report healthy -- never reached its runner, has
  # no log to read, and `logs kubelet` fails naming a log never registered.
  # That error is a true statement about the log and says
  # nothing about the service, so a reader gets an unexplained failure where the
  # actual finding is the state and the condition it is waiting on. ServiceList
  # carries both, for every service at once, and the reader role is allowed to
  # call it.
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"

  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.92 "$tmp/node"

  [ -s "$tmp/node/services.log" ]
  assert_file_contains ' 10.244.1.92 services' "$kubectl_calls"
  assert_file_contains '[capture exit code: 0]' "$tmp/node/services.log"
  rm -rf "$tmp"
}

@test "the guest link table is captured as yaml, because MTU is not a printed column" {
  # MTU is the value that decides whether the PMTU reading of a stalled transfer
  # is alive: a guest at 1500 over an encapsulated fabric drops exactly the large
  # segments while the small ones pass. LinkStatus carries it in the spec and
  # not in the resource's print columns, so the table form -- which is what the
  # command prints by default -- omits the one field this is read for.
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"

  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.92 "$tmp/node"

  # Named .yaml.log, not .yaml, and the suffix is the assertion. Every capture
  # here gets `[capture exit code: N]` appended by the shared helper, so a file
  # promising YAML would not parse as YAML -- and a reader who reaches for yq
  # and gets a parse error learns something about the tool rather than about the
  # cluster. The .log suffix matches every other consumer of that footer.
  [ -s "$tmp/node/links.yaml.log" ]
  [ ! -e "$tmp/node/links.yaml" ]
  assert_file_contains ' 10.244.1.92 get links -o yaml' "$kubectl_calls"
  rm -rf "$tmp"
}

@test "the two added reads carry the tighter bound and the two older ones do not" {
  # The phase budget is what makes this a decision rather than a detail. These
  # two reads sit inside the collector the budget's own guard sizes itself
  # against, so every second added here is a second taken from the collectors
  # gated after it. Both are single tabular RPCs that a reachable apid answers
  # at once, which is not the shape of the two reads beside them: a kernel ring
  # buffer and five hundred lines of service log are streams, and cutting those
  # to ten seconds would lose evidence on exactly the slow-apid worker the
  # capture exists for.
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"

  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.92 "$tmp/node"

  while IFS= read -r line; do
    case "$line" in
      *' dmesg') [ "${line#-k 5 20 }" != "$line" ] || { echo "dmesg lost its 20s bound: $line" >&2; exit 1 ;} ;;
      *'logs kubelet --tail=500') [ "${line#-k 5 20 }" != "$line" ] || { echo "kubelet log lost its 20s bound: $line" >&2; exit 1 ;} ;;
      *' services') [ "${line#-k 5 10 }" != "$line" ] || { echo "services must carry the tighter bound: $line" >&2; exit 1 ;} ;;
      *'get links -o yaml') [ "${line#-k 5 10 }" != "$line" ] || { echo "links must carry the tighter bound: $line" >&2; exit 1 ;} ;;
      *) echo "unexpected bounded call: $line" >&2; exit 1 ;;
    esac
  done <"$timeout_calls"
  [ "$(wc -l < "$timeout_calls")" -eq 4 ]
  rm -rf "$tmp"
}

@test "a worker that produced no guest evidence at all says so in its own directory" {
  # The gap this closes is a directory that looks like a worker with nothing to
  # report. When apid refuses the connection every read fails and each log holds
  # its own rpc error plus an exit code, which is accurate per file and adds up
  # to nothing a reader scanning the tree can see: the directory has the same
  # shape as one whose reads returned little. The marker states the outcome for
  # the worker rather than for each read, and it is written only when NOTHING
  # succeeded, so a worker that answered one read out of four is not written up
  # as unreachable.
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"
  timeout_rc=1

  rc=0
  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.93 "$tmp/node" || rc=$?

  # A zero status on the all-fail path, which is the ordinary shape of an
  # unreachable worker. Note what this assertion does NOT establish: the call
  # above uses `|| rc=$?`, which suppresses errexit for everything it covers, so
  # a failing statement inside the function would not abort here either way.
  # That the function keeps its caller's walk alive under a live errexit is a
  # property of the `|| true` on the marker write, and it is not observable from
  # this suite -- reaching it needs a bare call in a shell this file does not
  # provide.
  [ "$rc" -eq 0 ]
  assert_file_contains 'no Talos read completed for this worker' "$tmp/node/CAPTURE-FAILED.txt"
  # The per-read logs stay: they carry the reason, and the marker only points.
  assert_file_contains '[capture exit code: 1]' "$tmp/node/dmesg.log"
  rm -rf "$tmp"
}

@test "a worker that answered even one read carries no capture-failed marker" {
  # The positive control for the marker above, and it has to sit at the
  # BOUNDARY rather than at the easy end. The marker's contract is "nothing
  # answered", so the case that distinguishes it from every nearby threshold is
  # one read answering and three failing -- not four answering, which is what
  # this test used to stage and which any `answered < N` would also satisfy.
  # Measured: with the all-succeed staging, changing the condition to
  # `-lt 4` left this file green while a worker that answered three reads of
  # four was written up as having produced no guest evidence at all, which is
  # the false statement the marker exists to prevent.
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"
  # Everything but the service list fails, so exactly one read answers.
  timeout_fail_commands="dmesg kubelet links"

  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.92 "$tmp/node"

  [ ! -f "$tmp/node/CAPTURE-FAILED.txt" ]
  # Not vacuous: the staging really did leave three reads failing and one
  # answering, so the absence above is the boundary rather than an easy case.
  assert_file_contains '[capture exit code: 1]' "$tmp/node/dmesg.log"
  assert_file_contains '[capture exit code: 0]' "$tmp/node/services.log"
  rm -rf "$tmp"
}

@test "every one of the four reads counts toward the tally, not just the last" {
  # The other direction of the same boundary, and it has to walk all four
  # readings rather than restage the one above. The marker is decided by a COUNT
  # over four reads, so a read that stops contributing is invisible in any
  # staging where some other read answers -- it only changes which staging
  # reaches zero. Measured before this test existed: dropping the tally from
  # three of the four reads at once left the whole file green, because the one
  # staging in use happened to keep the fourth.
  #
  # So each read takes a turn as the sole answering one. Dropping any single
  # read's contribution makes its own turn report a worker that answered
  # nothing, while its own log in the same directory holds a clean capture.
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  for sole in dmesg kubelet links services; do
    tmp=$(mktemp -d)
    kubectl_calls="$tmp/kubectl.calls"
    timeout_calls="$tmp/timeout.calls"
    timeout_fail_commands=$(printf '%s\n' dmesg kubelet links services | grep -v "^${sole}$" | tr '\n' ' ')

    cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.92 "$tmp/node"

    case "$sole" in
      dmesg) log="dmesg.log" ;;
      kubelet) log="kubelet.log" ;;
      links) log="links.yaml.log" ;;
      services) log="services.log" ;;
    esac
    # Not vacuous: the staging really did leave this read as the only answer.
    assert_file_contains '[capture exit code: 0]' "$tmp/node/$log"
    if [ -f "$tmp/node/CAPTURE-FAILED.txt" ]; then
      echo "FAIL: with $sole the only answering read, the worker was declared to have produced nothing" >&2
      rm -rf "$tmp"
      false
    fi
    rm -rf "$tmp"
  done
}

@test "a timed-out worker remains recorded and does not block the next capture" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"
  timeout_rc=124

  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.93 "$tmp/node"

  [ "$(wc -l < "$kubectl_calls")" -eq 4 ]
  [ "$(wc -l < "$timeout_calls")" -eq 4 ]
  assert_file_contains '[capture exit code: 124]' "$tmp/node/dmesg.log"
  assert_file_contains '[capture exit code: 124]' "$tmp/node/kubelet.log"
  assert_file_contains '[capture exit code: 124]' "$tmp/node/services.log"
  rm -rf "$tmp"
}

@test "orchestrator continues from a timed-out worker to the next VMI" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  origin=$PWD
  kubectl_calls="$tmp/kubectl.calls"
  kubectl_manifest="$tmp/manifest.yaml"
  kubectl_vmi_json="$tmp/vmis.json"
  talosctl_calls="$tmp/talosctl.calls"
  timeout_calls="$tmp/timeout.calls"
  timeout_fail_node_ip=10.244.1.93
  COZY_REPORT_DIR="$tmp/report"
  COZY_SNAPSHOT_NAME=diagnostic-smoke
  printf '%s\n' '{"items":[{"metadata":{"name":"worker-no-ip"},"status":{"interfaces":[{"name":"default"}]}},{"metadata":{"name":"worker-a"},"status":{"interfaces":[{"name":"default","ipAddress":"10.244.1.93"}]}},{"metadata":{"name":"worker-b"},"status":{"interfaces":[{"name":"default","ipAddress":"10.244.1.94"}]}}]}' >"$kubectl_vmi_json"
  printf '%s\n' '#!/bin/sh' >"$tmp/talosctl"
  chmod 755 "$tmp/talosctl"
  cd "$tmp"

  cozy_capture_tenant_talos test-latest-version

  [ "$(awk '/ exec kubernetes-test-latest-version-talos-diagnostics / { count++ } END { print count + 0 }' "$kubectl_calls")" -eq 8 ]
  assert_file_contains 'default VMI interface has no reported IP address' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/worker-no-ip/capture-error.log"
  assert_file_contains '[capture exit code: 124]' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/worker-a/dmesg.log"
  assert_file_contains '[capture exit code: 124]' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/worker-a/kubelet.log"
  assert_file_contains '[capture exit code: 0]' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/worker-b/dmesg.log"
  assert_file_contains '[capture exit code: 0]' "$COZY_REPORT_DIR/snapshots/$COZY_SNAPSHOT_NAME/tenant-talos/worker-b/kubelet.log"
  # Back out of the scratch directory before removing it: this test runs the
  # orchestrator from inside it. Not because rm would fail -- BSD, busybox and
  # GNU rm all remove a directory from inside it and return 0 -- but because
  # the runner's own bookkeeping still runs in this subshell after the body
  # returns, and it would be doing so on an unlinked cwd.
  cd "$origin"
  rm -rf "$tmp"
}

@test "cleanup deletes diagnostic Certificate before its Pod and Secret" {
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  cozy_wait_linstor_pool_baseline() { return 0; }
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"

  cozy_cleanup test-latest-version

  [ "$(sed -n '2p' "$kubectl_calls")" = '-n tenant-test delete certificates.cert-manager.io -l cozystack-e2e.io/tenant-talos-diagnostics --ignore-not-found --wait=true --timeout=30s' ]
  [ "$(sed -n '3p' "$kubectl_calls")" = '-n tenant-test delete pod,secret -l cozystack-e2e.io/tenant-talos-diagnostics --ignore-not-found --wait=false' ]
  rm -rf "$tmp"
}

@test "a worker whose reads were cut short is not told its directory is empty" {
  # The slow-apid worker, and the shape this capture is most often reached on:
  # every read is killed at its bound after writing part of what it fetched, so
  # nothing exits zero and the tally reads nothing-completed -- while the
  # directory holds real evidence. A marker saying nothing was observed would be
  # this collector making the unsupported claim its own status arms exist to
  # prevent, one directory up.
  . hack/e2e-chainsaw/_lib/run-kubernetes.sh
  tmp=$(mktemp -d)
  kubectl_calls="$tmp/kubectl.calls"
  timeout_calls="$tmp/timeout.calls"
  # Output first, then the non-zero status: what `timeout` does to a stream it
  # kills, and the reason the mock cannot return before writing.
  timeout_rc=124

  cozy_capture_tenant_talos_node diagnostics-pod 10.244.1.93 "$tmp/node"

  # Not vacuous: a read really did leave content behind before it was killed.
  assert_file_contains 'mock capture for' "$tmp/node/dmesg.log"
  assert_file_contains '[capture exit code: 124]' "$tmp/node/dmesg.log"
  # The marker still fires -- nothing completed -- but says only that.
  assert_file_contains 'no Talos read completed for this worker' "$tmp/node/CAPTURE-FAILED.txt"
  assert_file_lacks_pattern 'nothing here was observed' "$tmp/node/CAPTURE-FAILED.txt"
  rm -rf "$tmp"
}