Newer
Older
openstack-caracal-dc-dc / tests / dc-cache-proxy / run-tests.sh
#!/usr/bin/env bash
# tests/dc-cache-proxy/run-tests.sh -- guard for scripts/dc-cache-proxy.sh
# (D-135 amendment 2026-07-24: the INTERIM per-DC apt CACHING PROXY, apt-cacher-ng,
# the DC1-unblock path). Static: syntax, both sites' identity values (the utility
# .4 the deploy expects + the ruled edge gateways -- changing one silently breaks
# the proxy address or upstream egress), stable-identity keying (libvirt net
# names, never virbrN), proxy-mode port pin, the MEASURED-tag discipline on site
# rows, arg/site validation, and that the net layer is REUSED (not re-created).
# Exit: 0 all pass | 1 any case failed. ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$(cd "$HERE/../.." && pwd)/scripts/dc-cache-proxy.sh"
PASS=0; FAIL=0
ok(){ echo "  PASS  $1"; PASS=$((PASS+1)); }
no(){ echo "  FAIL  $1"; FAIL=$((FAIL+1)); }

[ -f "$SCRIPT" ] && ok "T1 script present" || { no "T1 script present"; echo; echo "dc-cache-proxy: $PASS passed, $FAIL failed"; exit 1; }
bash -n "$SCRIPT" 2>/dev/null && ok "T2 bash -n clean" || no "T2 bash -n clean"

# T3-T4: dc0 identity values (utility .4 + ruled edge gw) -- parity row
grep -q 'LISTEN_CIDR="10.12.8.4/22"' "$SCRIPT" \
  && grep -q 'UTIL_NET="vr1-dc0-metal-admin"' "$SCRIPT" \
  && ok "T3 dc0 LISTEN 10.12.8.4/22 on metal-admin (utility .4)" \
  || no "T3 dc0 LISTEN 10.12.8.4/22 on metal-admin (utility .4)"
awk '/^  dc0\)/{f=1} f{print} f&&/;;/{exit}' "$SCRIPT" | grep -q 'EDGE_GW="10.12.4.1"' \
  && ok "T4 dc0 edge gw 10.12.4.1" || no "T4 dc0 edge gw 10.12.4.1"

# T5-T6: dc1 identity values (the INTERIM cache-proxy site)
grep -q 'LISTEN_CIDR="10.12.68.4/22"' "$SCRIPT" \
  && grep -q 'UTIL_NET="vr1-dc1-metal-admin"' "$SCRIPT" \
  && ok "T5 dc1 LISTEN 10.12.68.4/22 on metal-admin (utility .4)" \
  || no "T5 dc1 LISTEN 10.12.68.4/22 on metal-admin (utility .4)"
awk '/^  dc1\)/{f=1} f{print} f&&/;;/{exit}' "$SCRIPT" | grep -q 'EDGE_GW="10.12.64.1"' \
  && ok "T6 dc1 edge gw 10.12.64.1" || no "T6 dc1 edge gw 10.12.64.1"

# T7: stable-identity keying -- no virbrN literal; bridge from the net name
grep -q 'net-dumpxml' "$SCRIPT" && ! grep -qE 'virbr[0-9]' "$SCRIPT" \
  && ok "T7 bridge resolved from net name; no virbrN literal" \
  || no "T7 bridge resolved from net name; no virbrN literal"

# T8: proxy-mode port pinned to 3142
grep -q 'PROXY_PORT="3142"' "$SCRIPT" && grep -q 'Port: ${PROXY_PORT}' "$SCRIPT" \
  && ok "T8 apt-cacher-ng port pinned 3142" || no "T8 apt-cacher-ng port pinned 3142"

# T9: consumption documented as proxy mode (apt-http-proxy), not apt-mirror
grep -q 'apt-http-proxy=http://' "$SCRIPT" \
  && ok "T9 documents proxy-mode consumption (apt-http-proxy)" \
  || no "T9 documents proxy-mode consumption (apt-http-proxy)"

# T10: behavioral smoke test covers BOTH archive and UCA through the proxy
grep -q 'archive.ubuntu.com' "$SCRIPT" && grep -q 'ubuntu-cloud.archive.canonical.com' "$SCRIPT" \
  && grep -q -- '-x "http://${LISTEN}:${PROXY_PORT}"' "$SCRIPT" \
  && ok "T10 smoke test proxies archive + UCA" || no "T10 smoke test proxies archive + UCA"

# T11: install requires root
grep -q 'install requires root' "$SCRIPT" && ok "T11 install requires root" || no "T11 install requires root"

# T12 (REPLACED 2026-07-27 -- it asserted the SUPERSEDED design). It used to require
# that this script REUSE dc-mirror.sh's net layer and write no ip addr/route itself.
# That coupling was a measured trap: on a proxy-only DC, three `*-mirror-*` files were
# load-bearing for the PROXY, so the recorded mirror-removal procedure would have killed
# it, and the proxy could not be rebuilt without reinstalling the whole mirror apparatus.
# Operator ruled the net layer moves here (2026-07-27), so the test is INVERTED rather
# than dropped -- the old assertion is now the defect it protected against.
# T12: this script OWNS the net layer -- it writes the address and route itself...
grep -qE 'ip addr replace' "$SCRIPT" && grep -qE 'ip route replace default' "$SCRIPT" \
  && ok "T12 net layer is OWNED here (writes its own addr + default route)" \
  || no "T12 net layer is OWNED here (writes its own addr + default route)"

# T12b: ...and no longer defers to dc-mirror.sh for it. A lingering "run dc-mirror.sh
# install" instruction would send an operator to re-create the very apparatus a
# proxy-only DC is meant not to have. Scoped to NON-COMMENT lines on purpose: the
# header's account of WHY the coupling was removed cites that command, and deleting
# the history to satisfy a grep would lose the reason the trap existed. What must not
# survive is an OPERATIVE referral -- one reachable at runtime in an echo/miss string.
grep -vE '^[[:space:]]*#' "$SCRIPT" | grep -q 'dc-mirror.sh install' \
  && no "T12b no OPERATIVE dc-mirror.sh referral remains (header history is exempt)" \
  || ok "T12b no OPERATIVE dc-mirror.sh referral remains (header history is exempt)"

# T12c: the owned artifacts are named for the PROXY, never "mirror". The whole point
# is that a proxy-only DC carries no mirror-named dependency for someone to tidy away.
grep -q 'cache-proxy-net.service' "$SCRIPT" \
  && grep -q 'cache-proxy-net-apply' "$SCRIPT" \
  && ! grep -qE '(NET_UNIT|NET_HELPER|RESOLVED_DROPIN)=.*mirror' "$SCRIPT" \
  && ok "T12c owned net artifacts are proxy-named, not mirror-named" \
  || no "T12c owned net artifacts are proxy-named, not mirror-named"

# T12d: the net layer must be ENABLED, not merely started -- otherwise the proxy's
# LISTEN address and egress vanish on the next rack reboot. This rack has rebooted
# before (the D-126/D-128 base-leg machinery exists for exactly that reason).
grep -qE 'systemctl enable "\$\{SITE\}-cache-proxy-net.service"' "$SCRIPT" \
  && grep -q 'cache-proxy-net.service enabled' "$SCRIPT" \
  && ok "T12d net unit is enabled at install and asserted by check (reboot-persistent)" \
  || no "T12d net unit is enabled at install and asserted by check (reboot-persistent)"

# T12e: check must assert the ARTIFACTS, not just the live address/route. Asserting
# only the live effect would PASS while the layer was really provided by
# <site>-mirror-net.service -- it would look independent and break on mirror teardown,
# which is the exact failure this change exists to prevent.
grep -q 'gen_net_helper' "$SCRIPT" && grep -q 'gen_net_unit' "$SCRIPT" \
  && grep -q 'this script owns the net layer now' "$SCRIPT" \
  && ok "T12e check asserts the owned net artifacts, not only their live effect" \
  || no "T12e check asserts the owned net artifacts, not only their live effect"

# T12f: idempotent verbs only. `ip addr replace`/`ip route replace` let this unit and
# a surviving <site>-mirror-net.service coexist on a site running both (dc0 keeps its
# mirror), setting identical MEASURED values instead of fighting.
! grep -qE 'ip (addr|route) add ' "$SCRIPT" \
  && ok "T12f net writes use idempotent replace (safe to coexist with the mirror unit)" \
  || no "T12f net writes use idempotent replace (safe to coexist with the mirror unit)"

# T13: MEASURED/RULED-tag discipline on every site row (hard rule 2)
MR="$(grep -cE 'MEASURED/RULED' "$SCRIPT")"
[ "$MR" -ge 2 ] && ok "T13 both site rows carry a MEASURED/RULED tag" \
  || no "T13 both site rows carry a MEASURED/RULED tag (found $MR)"

# T14: bad args / unknown site -> exit 2
out="$(bash "$SCRIPT" bogus dc1 2>&1)"; [ "$?" = "2" ] || out="$out RC!=2"
grep -q 'usage:' <<<"$out" && ok "T14 bad mode -> usage, exit 2" || no "T14 bad mode -> usage, exit 2"
bash "$SCRIPT" check zz >/dev/null 2>&1; [ "$?" = "2" ] \
  && ok "T15 unknown site -> exit 2" || no "T15 unknown site -> exit 2"

# ===========================================================================
# T16-T25 (2026-07-29): the NODE-SIDE subcommand -- gate G17.
# G17's dc1 half named `dc-cache-proxy.sh:210-217` as its node-side check, but that
# had no entry point and sat inside `check`, which asserts three RACK-side facts a
# node cannot satisfy. These cases drive `node dc1` behaviorally against a fake
# curl/chronyc on PATH, because a grep cannot tell a gate that fails from one that
# cannot. NOTE what this does NOT prove: no DC node has run it -- the nodes are
# powered off in Ready and G17's window is Stage-5 first boot -- so the GREEN below
# is a fixture green, not a node green.
# ===========================================================================
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
FB="$TMP/bin"; mkdir -p "$FB"
cat > "$FB/curl" <<'FAKE'
#!/usr/bin/env bash
# fake curl: honours -o <file> and returns %{http_code} on stdout, like the real one.
out=""; url=""
while [ $# -gt 0 ]; do
  case "$1" in
    -o) out="$2"; shift 2 ;;
    -w|-x|-m) shift 2 ;;
    -*) shift ;;
    *)  url="$1"; shift ;;
  esac
done
dist="${url#*/dists/}"; dist="${dist%/Release}"
# Body shape MEASURED 2026-07-29 against both real upstreams (see the script header).
release() { printf 'Origin: Ubuntu\nSuite: x\nCodename: %s\nComponents: main\nMD5Sum:\n abc123 42 main/binary-amd64/Packages\n' "$1" > "$out"; }
case "$(cat "$FAKEDIR/curl.mode")" in
  ok)         release "$dist";        printf '200' ;;
  wrongdist)  release "bookworm";     printf '200' ;;
  nosum)      printf 'Codename: %s\nComponents: main\n' "$dist" > "$out"; printf '200' ;;
  html)       printf '<html><body>apt-cacher-ng report</body></html>\n' > "$out"; printf '200' ;;
  http404)    printf 'not found\n' > "$out"; printf '404' ;;
  unreach)    echo "curl: (7) Failed to connect to 10.12.68.4 port 3142" >&2; exit 7 ;;
esac
FAKE
cat > "$FB/chronyc" <<'FAKE'
#!/usr/bin/env bash
case "$(cat "$FAKEDIR/chrony.mode")" in
  rack)  printf 'MS Name/IP address   Stratum Poll Reach LastRx Last sample\n^* 10.12.68.2   3  6  377  21  +12us\n' ;;
  edge)  printf 'MS Name/IP address   Stratum Poll Reach LastRx Last sample\n^* 10.12.64.1   3  6  377  21  +12us\n' ;;
  other) printf 'MS Name/IP address   Stratum Poll Reach LastRx Last sample\n^* 91.189.94.4  2  6  377  21  +9us\n' ;;
  empty) : ;;
  err)   echo "506 Cannot talk to daemon" >&2; exit 1 ;;
esac
FAKE
chmod +x "$FB/curl" "$FB/chronyc"

noderun() { # noderun <want_rc> <regex> <label> <curl.mode> <chrony.mode>
  local want="$1" rx="$2" label="$3" cm="$4" hm="$5" out rc
  printf '%s' "$cm" > "$TMP/curl.mode"; printf '%s' "$hm" > "$TMP/chrony.mode"
  out="$(env FAKEDIR="$TMP" PATH="$FB:$PATH" bash "$SCRIPT" node dc1 2>&1)"; rc=$?
  if [ "$rc" = "$want" ] && grep -qE "$rx" <<<"$out"; then ok "$label"
  else no "$label (rc=$rc want=$want)"; sed 's/^/        /' <<<"$out" | head -6; fi
}

# T16 GREEN: both artifacts served with real Release CONTENT, time from the MAAS rack
noderun 0 'dc-cache-proxy node \(dc1\): PASS' \
  "T16 node: proxied archive+UCA Release + rack time source -> PASS" ok rack
# T17 the defect the superseded G17 wording could not catch: a 200 with an HTML body
noderun 1 'not a Release file' "T17 node: HTTP 200 with an HTML body FAILS (200 is not content)" html rack
# T18 a 200 serving the WRONG dist's Release -- content, not just a status line
noderun 1 "not that dist's Release file" "T18 node: 200 with the wrong dist's Release FAILS" wrongdist rack
# T19 a Release-looking body with no checksum section is not a package index
noderun 1 'no checksum section' "T19 node: Release body without a checksum section FAILS" nosum rack
# T20 a real 404 through the proxy
noderun 1 'returned HTTP 404' "T20 node: HTTP 404 through the proxy FAILS" http404 rack
# T21 REFUSAL: proxy unreachable is 'could not look', never 'nothing there'
noderun 3 'unreachable is not' "T21 node: unreachable proxy REFUSES (rc 3, not 0)" unreach rack
# T22 time source is the DC edge -- D-129(iv) gives the edge no NTP role
noderun 1 'includes the DC edge 10.12.64.1' "T22 node: syncing from the DC edge FAILS (D-129(iv))" ok edge
# T23 REFUSAL: an unrecognised time source refuses rather than defaulting to success
noderun 3 'unrecognised time source' "T23 node: unknown time source REFUSES" ok other
# T24 REFUSAL: chronyc answering nothing is UNKNOWN, not absent
noderun 3 'time source is UNKNOWN' "T24 node: silent chronyc REFUSES" ok empty
# T25 the node subcommand must touch NOTHING rack-side -- no root, no systemctl, no
#     virsh, no generated-file diffs. That is the whole reason `check` cannot serve.
BODY="$(awk '/^do_node\(\) \{/{f=1} f{print} f&&/^\}/{exit}' "$SCRIPT")"
if grep -qE 'systemctl|virsh|util_bridge|id -u|ip (addr|route)|diff -q' <<<"$BODY"; then
  no "T25 node body is node-satisfiable only (no rack-side calls)"
else ok "T25 node body is node-satisfiable only (no rack-side calls)"; fi

echo; echo "dc-cache-proxy: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ] && exit 0 || exit 1