Newer
Older
openstack-caracal-dc-dc / tests / site-tailscale / run-tests.sh
#!/usr/bin/env bash
# tests/site-tailscale/run-tests.sh -- offline harness for scripts/site-tailscale.sh.
# Fakes `tailscale` (status --json / up / version), `ip`, `sysctl` (forwarding), `dpkg` (prep
# install); real python3 for jget. NO network, mutates nothing outside mktemp. Proves each exit
# path can FIRE (a new assertion is decoration until it has a failing-direction fixture):
#   prep-happy      -> 0   (pkg present + forwarding on)
#   prep-fromdeb    -> 0   (pkg absent, $TS_DEB staged, dpkg installs it, forwarding on)
#   prep-noforward  -> 4   (forwarding sysctl reads 0 -- the load-bearing subnet-router property)
#   prep-nopkg-nodeb-> 4   (pkg absent and no $TS_DEB staged)
#   check-happy     -> 0   (Running + route approved + forwarding + untagged + .7 leg)
#   check-notrun    -> 1   (BackendState != Running)
#   check-noroute   -> 1   (metal-admin CIDR not in approved PrimaryRoutes)
#   check-noforward -> 1   (IP forwarding off -- green everywhere else, forwards nothing)
#   check-noleg     -> 1   (no .7 metal-admin leg on the host)
#   check-tag-happy -> 0   (TS_TAG set + node tagged)
#   check-tag-notag -> 1   (TS_TAG set but node NOT tagged)
#   install-happy   -> 0   (untagged up ok + check green)
#   install-tag-happy-> 0  (tagged up ok + check green)
#   install-nokey   -> 4   (no pre-auth key)
#   install-noTS    -> 4   (tailscale binary absent -> "run prep first")
#   install-upfail  -> 4   (tailscale up rejected)
#   badmode/badsite/noargs -> 2
#   dc1 resolves its own CIDR (10.12.68.0/22), not dc0's
#   site-table rows carry MEASURED tags (hard rule 2)
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
SUT="$REPO/scripts/site-tailscale.sh"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
BIN="$TMP/bin"; mkdir -p "$BIN" "$TMP/sysctl.d"
printf 'fake-deb-content\n' > "$TMP/fake.deb"

# ---- fake tailscale --------------------------------------------------------
cat > "$BIN/tailscale" <<'FB'
#!/usr/bin/env bash
mode="${TS_FAKE_MODE:-happy}"; cidr="${TS_FAKE_CIDR:-10.12.8.0/22}"
case "$1" in
  version) echo "1.98.9"; exit 0;;
  up) [ "$mode" = upfail ] && exit 1; exit 0;;
  status)
     # $2 == --json
     st='"Running"'; routes="[\"$cidr\"]"
     tags='[]'; [ -n "${TS_TAG:-}" ] && tags="[\"$TS_TAG\"]"
     case "$mode" in
       notrun)  st='"Stopped"';;
       noroute) routes='[]';;
       notag)   tags='[]';;
     esac
     printf '{"BackendState":%s,"Self":{"PrimaryRoutes":%s,"Tags":%s}}\n' "$st" "$routes" "$tags"
     exit 0;;
  *) exit 0;;
esac
FB
chmod +x "$BIN/tailscale"

# ---- fake ip (own_leg_matches) --------------------------------------------
cat > "$BIN/ip" <<'FB'
#!/usr/bin/env bash
# emit a .7 metal-admin leg unless TS_FAKE_MODE=noleg
[ "${TS_FAKE_MODE:-}" = noleg ] && { echo "1: lo inet 127.0.0.1/8 scope host lo"; exit 0; }
echo "2: enp1s0 inet ${TS_FAKE_LEG:-10.12.8.7}/22 scope global enp1s0"
exit 0
FB
chmod +x "$BIN/ip"

# ---- fake sysctl (forwarding) ---------------------------------------------
cat > "$BIN/sysctl" <<'FB'
#!/usr/bin/env bash
fwd="${TS_FAKE_FWD:-1}"
case "$*" in
  "--system") exit 0;;
  "-n net.ipv4.ip_forward") echo "$fwd"; exit 0;;
  "-n net.ipv6.conf.all.forwarding") echo "$fwd"; exit 0;;
  *) exit 0;;
esac
FB
chmod +x "$BIN/sysctl"

# ---- fake dpkg (prep install-from-deb) ------------------------------------
# "installs" by copying the fake tailscale to $TS_INSTALL_TARGET so command -v succeeds after.
cat > "$BIN/dpkg" <<FB
#!/usr/bin/env bash
if [ "\$1" = "-i" ] && [ -n "\${TS_INSTALL_TARGET:-}" ]; then
  cp "$BIN/tailscale" "\$TS_INSTALL_TARGET" && chmod +x "\$TS_INSTALL_TARGET"; exit 0
fi
exit 0
FB
chmod +x "$BIN/dpkg"

run(){ # <name> <expected-exit> <mode> <args...>
  local name="$1" exp="$2" mode="$3"; shift 3
  local out code
  out="$(PATH="$BIN:$PATH" \
         TAILSCALE="${CASE_TS:-$BIN/tailscale}" \
         SYSCTL="$BIN/sysctl" DPKG="$BIN/dpkg" APTGET="/bin/false" \
         TS_SYSCTL_D="$TMP/sysctl.d" \
         TS_FAKE_MODE="$mode" TS_FAKE_FWD="${CASE_FWD:-1}" \
         TS_FAKE_CIDR="${CASE_CIDR:-10.12.8.0/22}" TS_FAKE_LEG="${CASE_LEG:-10.12.8.7}" \
         TS_TAG="${CASE_TAG-}" TS_AUTHKEY="${CASE_AUTHKEY:-}" \
         TS_DEB="${CASE_DEB:-}" TS_INSTALL_TARGET="${CASE_TS:-}" \
         bash "$SUT" "$@" 2>&1)"; code=$?
  if [ "$code" -eq "$exp" ]; then PASS=$((PASS+1)); echo "PASS: $name (exit $code)"
  else FAIL=$((FAIL+1)); echo "FAIL: $name (got $code want $exp)"; echo "$out" | sed 's/^/    /'; fi
}

echo "=== site-tailscale harness ==="
# prep cases
run prep-happy       0 happy prep vr1-dc0
CASE_TS="$BIN/staged-ts" CASE_DEB="$TMP/fake.deb" run prep-fromdeb 0 happy prep vr1-dc0
CASE_FWD=0 run prep-noforward 4 happy prep vr1-dc0
CASE_TS="$BIN/nope" run prep-nopkg-nodeb 4 happy prep vr1-dc0
# check cases (default untagged)
run check-happy      0 happy   check vr1-dc0
run check-notrun     1 notrun  check vr1-dc0
run check-noroute    1 noroute check vr1-dc0
CASE_FWD=0 run check-noforward 1 happy check vr1-dc0
run check-noleg      1 noleg   check vr1-dc0
# tagged mode (opt-in via TS_TAG)
CASE_TAG="tag:subnet-router" run check-tag-happy 0 happy check vr1-dc0
CASE_TAG="tag:subnet-router" run check-tag-notag 1 notag check vr1-dc0
# install cases
CASE_AUTHKEY="tskey-fake" run install-happy 0 happy install vr1-dc0
CASE_TAG="tag:subnet-router" CASE_AUTHKEY="tskey-fake" run install-tag-happy 0 happy install vr1-dc0
                          run install-nokey  4 happy  install vr1-dc0
CASE_AUTHKEY="tskey-fake" run install-upfail 4 upfail install vr1-dc0
CASE_TS="$BIN/nope" CASE_AUTHKEY="tskey-fake" run install-noTS 4 happy install vr1-dc0
# arg/site validation
run badmode  2 happy frobnicate vr1-dc0
run badsite  2 happy check      vr1-dc9
run noargs   2 happy

# dc1 resolves its OWN cidr (not dc0's) -- run check-happy with dc1 fixture
CASE_CIDR='10.12.68.0/22' CASE_LEG='10.12.68.7' run dc1-cidr 0 happy check vr1-dc1
out="$(PATH="$BIN:$PATH" TAILSCALE="$BIN/tailscale" SYSCTL="$BIN/sysctl" TS_SYSCTL_D="$TMP/sysctl.d" \
       TS_FAKE_MODE=happy TS_FAKE_CIDR='10.12.68.0/22' TS_FAKE_LEG='10.12.68.7' \
       bash "$SUT" check vr1-dc1 2>&1)"
if printf '%s' "$out" | grep -q '10.12.68.0/22'; then PASS=$((PASS+1)); echo "PASS: dc1 emits 10.12.68.0/22"
else FAIL=$((FAIL+1)); echo "FAIL: dc1 site cidr"; echo "$out" | sed 's/^/    /'; fi

# site-table rows must carry MEASURED tags (hard rule 2)
for s in 'vr1-dc0.*MEASURED' 'vr1-dc1.*MEASURED'; do
  if grep -qE "$s" "$SUT"; then PASS=$((PASS+1)); echo "PASS: site row tagged MEASURED ($s)"
  else FAIL=$((FAIL+1)); echo "FAIL: site row missing MEASURED ($s)"; fi
done

echo "=== $PASS pass / $FAIL fail ==="
[ "$FAIL" -eq 0 ]