diff --git a/docs/v1-redeploy-changelog.md b/docs/v1-redeploy-changelog.md index 656ef67..f0ffb61 100644 --- a/docs/v1-redeploy-changelog.md +++ b/docs/v1-redeploy-changelog.md @@ -694,5 +694,34 @@ `bash scripts/...`; or (b) one-time `git update-index --chmod=+x scripts/*.sh tests/*/run-tests.sh tests/*/fakebin/*` from Git Bash + commit (writes 755 into the tree). Not yet actioned. +### Phase-05 prep -- octavia verify deliverable + entry audit + DOCFIX-049 (2026-06-27, pre-execution) +Entry audit GREEN (read-only verify-before-mutate): octavia/0 blocked "Awaiting configure-resources", +agent idle, on 3/lxd/3 addr 10.12.12.112 (metal-internal data leg); configure-resources action present; +config gate holds (retrofit use-internal-endpoints=true / image-format=raw / amp-image-tag=octavia-amphora; +octavia amp-image-tag=octavia-amphora -- match); charm-octavia net/subnet/sg all EMPTY; amphora provider +present; no pre-existing octavia-amphora image; no configure-resources operation yet (not fired). PROCEED to 5.1. + +New deliverable staged: + scripts/phase-05-octavia-verify.sh -- read-only auto-detecting verify. CONFIG GATE always + (use-internal-endpoints=true / image-format=raw / amp-image-tag match -- LP#1937003 + Ceph-RBD). + Then by octavia/0 workload status: blocked -> PRE (resources empty) -> PROCEED; active -> POST + (lb-mgmt net/subnet/sg present + o-hm0 UP w/ fc00::/ ULA) then amphora image tagged+ACTIVE? -> + CONTROL-PLANE-DONE (run 5.2) or PASS. Sources lib-net.sh + need_jq; requires admin-openrc + juju. + Exit 0 PROCEED|CONTROL-PLANE-DONE|PASS / 1 HOLD|FAIL / 2 precondition. Mutates nothing. + tests/phase-05/ -- offline regression (real jq + fake juju/openstack shims). 8/8 green: PROCEED, + CONTROL-PLANE-DONE, PASS, and five failures (amp-image-tag mismatch; image-format!=raw; active-but- + resources-missing; o-hm0 down; blocked-but-resources-present anomaly). bash -n + shellcheck clean; + ASCII + 0 CR on all five. + +DOCFIX-049 -- phase-05 do-doc internal-glance-vip is pre-D-052 (.8.53 -> .12.53). + The do-doc's ENV(internal-glance-vip) reads 10.12.8.53 and prose says "retrofit is metal-only + 10.12.8.x -> internal glance". Post-D-052 the glance INTERNAL endpoint is 10.12.12.53 (metal-internal); + .8.53 is now the ADMIN VIP (bundle glance vip "10.12.4.53 10.12.8.53 10.12.12.53"; same class as + DOCFIX-045). Reference-text ONLY: the 5.2 block does NOT hardcode the glance VIP (the retrofit + discovers it via use-internal-endpoints=true), and connectivity holds -- octavia/0 is on the + metal-internal leg (10.12.12.112), so it reaches .12.53. FIX (consolidation): update the do-doc + ENV + prose .8.53 -> .12.53 (metal-internal) per D-052. Watch the retrofit --wait=30m output to + confirm it reaches internal glance. Non-blocking. + ### Next-free numbers -Design decision: D-056. Doc fix: DOCFIX-049. +Design decision: D-056. Doc fix: DOCFIX-050. diff --git a/scripts/phase-05-octavia-verify.sh b/scripts/phase-05-octavia-verify.sh new file mode 100644 index 0000000..92eb3c5 --- /dev/null +++ b/scripts/phase-05-octavia-verify.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# scripts/phase-05-octavia-verify.sh [MODEL] +# +# Read-only verify for phase-05 (Octavia enablement, D-021). Auto-detects where the +# phase is and reports accordingly; mutates NOTHING, safe to re-run: +# +# CONFIG GATE (always): the LP#1937003 / Ceph-RBD preconditions -- +# octavia-diskimage-retrofit use-internal-endpoints=true, image-format=raw, +# amp-image-tag set AND == octavia amp-image-tag. A mismatch HOLDs regardless of state. +# +# then, by octavia/0 workload status: +# blocked -> PRE: charm-octavia resources must be EMPTY -> PROCEED (run 5.1 configure-resources) +# active -> POST: lb-mgmt net/subnet/sec-grp present + o-hm0 UP w/ fc00::/ ULA; +# then amphora image tagged $OTAG ACTIVE? +# no -> CONTROL-PLANE-DONE (5.1 done; run 5.2 amphora pipeline) +# yes -> PASS (phase-05 EXIT GATE met) +# other -> HOLD (in flight / unexpected; re-check, do not re-fire 5.1) +# +# Usage: source ~/admin-openrc && scripts/phase-05-octavia-verify.sh [MODEL] +# Exit: 0 PROCEED|CONTROL-PLANE-DONE|PASS | 1 HOLD/FAIL | 2 precondition +# +# Resolves dynamically; tags read from charm config (not hardcoded). Read-only. ASCII + LF. + +set -euo pipefail +shopt -s inherit_errexit 2>/dev/null || true +IFS=$'\n\t' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/lib-net.sh +. "$SCRIPT_DIR/lib-net.sh" + +MODEL="${1:-openstack}" +RETRO="octavia-diskimage-retrofit" + +FATAL=0 +fail() { echo "FAIL: $*" >&2; FATAL=$((FATAL + 1)); } +pass() { echo "PASS: $*"; } + +# --- preconditions ------------------------------------------------------------------ +need_jq || exit 2 +command -v openstack >/dev/null 2>&1 || { echo "FAIL: openstack client not found" >&2; exit 2; } +command -v juju >/dev/null 2>&1 || { echo "FAIL: juju client not found" >&2; exit 2; } +[ -n "${OS_AUTH_URL:-}" ] || { echo "FAIL: OS_AUTH_URL unset -- 'source ~/admin-openrc' first" >&2; exit 2; } + +J="$(juju status octavia -m "$MODEL" --format json 2>/dev/null || true)" +printf '%s' "$J" | jq -e '.applications.octavia.units."octavia/0"' >/dev/null 2>&1 \ + || { echo "FAIL: octavia/0 not found in model '$MODEL'" >&2; exit 2; } + +echo "=== phase-05 octavia-enablement verify (read-only) ===" +echo + +# --- CONFIG GATE (LP#1937003 + Ceph-RBD; always) ----------------------------------- +echo "--- config gate (use-internal-endpoints / image-format / amp-image-tag match) ---" +UIE="$(juju config "$RETRO" use-internal-endpoints 2>/dev/null || true)" +IMGFMT="$(juju config "$RETRO" image-format 2>/dev/null || true)" +RTAG="$(juju config "$RETRO" amp-image-tag 2>/dev/null || true)" +OTAG="$(juju config octavia amp-image-tag 2>/dev/null || true)" +echo " retrofit use-internal-endpoints=$UIE image-format=$IMGFMT amp-image-tag=$RTAG ; octavia amp-image-tag=$OTAG" +[ "$UIE" = "true" ] || fail "retrofit use-internal-endpoints=$UIE (need true; retrofit is metal-only -> internal glance)" +[ "$IMGFMT" = "raw" ] || fail "retrofit image-format=$IMGFMT (need raw; Ceph RBD fast-clone)" +{ [ -n "$RTAG" ] && [ "$RTAG" = "$OTAG" ]; } || fail "amp-image-tag mismatch retrofit='$RTAG' octavia='$OTAG' (LP#1937003)" +[ "$FATAL" -eq 0 ] && pass "config gate clear" +echo + +# --- octavia workload state --------------------------------------------------------- +OSTATE="$(printf '%s' "$J" | jq -r '.applications.octavia.units."octavia/0"."workload-status".current // "unknown"')" +OMSG="$(printf '%s' "$J" | jq -r '.applications.octavia.units."octavia/0"."workload-status".message // ""')" +echo "--- octavia/0 workload: $OSTATE ($OMSG) ---" + +# helper: count non-empty Name rows of a charm-octavia-tagged list +count_tagged() { "$@" --tags charm-octavia -f value -c Name 2>/dev/null | grep -c . || true; } + +if [ "$OSTATE" = "blocked" ]; then + # PRE: resources must be empty (idempotency baseline) + n_net="$(count_tagged openstack network list)" + n_sub="$(count_tagged openstack subnet list)" + n_sg="$(count_tagged openstack security group list)" + echo " charm-octavia resources: networks=$n_net subnets=$n_sub sec-groups=$n_sg (expect 0/0/0 pre-run)" + if [ "$n_net" -ne 0 ] || [ "$n_sub" -ne 0 ] || [ "$n_sg" -ne 0 ]; then + fail "octavia blocked but charm-octavia resources already exist -- inconsistent; investigate before re-firing" + fi + echo + if [ "$FATAL" -ne 0 ]; then echo "Summary: HOLD/FAIL -- $FATAL gate(s) failed."; exit 1; fi + echo "Summary: PROCEED -- config gate clear, octavia awaiting configure-resources (resources empty)." + echo " Next: juju run octavia/leader configure-resources -m $MODEL --wait=20m (Step 5.1)" + exit 0 + +elif [ "$OSTATE" = "active" ]; then + # POST: control-plane resources + o-hm0 + n_net="$(count_tagged openstack network list)" + n_sub="$(count_tagged openstack subnet list)" + n_sg="$(count_tagged openstack security group list)" + echo " charm-octavia resources: networks=$n_net subnets=$n_sub sec-groups=$n_sg (expect >=1 each)" + { [ "$n_net" -ge 1 ] && [ "$n_sub" -ge 1 ] && [ "$n_sg" -ge 1 ]; } \ + || fail "octavia active but charm-octavia lb-mgmt resources missing (net/sub/sg = $n_net/$n_sub/$n_sg)" + OHM0="$(juju exec --unit octavia/0 -m "$MODEL" -- 'ip -br addr show o-hm0' /dev/null || true)" + if printf '%s' "$OHM0" | grep -q 'UP' && printf '%s' "$OHM0" | grep -q 'fc00:'; then + pass "o-hm0 UP with fc00::/ IPv6-ULA" + else + fail "o-hm0 not UP with an fc00::/ ULA addr (got: ${OHM0:-})" + fi + # 5.2: amphora image + AMPH_ACTIVE="$(openstack image list --tag "$OTAG" -f value -c Status 2>/dev/null | grep -xc active || true)" + echo " images tagged $OTAG with Status=active: $AMPH_ACTIVE" + echo + if [ "$FATAL" -ne 0 ]; then echo "Summary: HOLD/FAIL -- $FATAL gate(s) failed."; exit 1; fi + if [ "$AMPH_ACTIVE" -ge 1 ]; then + echo "Summary: PASS -- phase-05 EXIT GATE met (octavia active; lb-mgmt resources + o-hm0 up; ACTIVE amphora tagged $OTAG)." + exit 0 + fi + echo "Summary: CONTROL-PLANE-DONE -- Step 5.1 complete; no ACTIVE amphora image yet." + echo " Next: run Step 5.2 (amphora image pipeline), then re-run this for PASS." + exit 0 + +else + echo + echo "Summary: HOLD -- octavia/0 workload '$OSTATE' (not blocked/active). In flight or unexpected;" + echo " re-check; do NOT re-fire configure-resources on a transient (appendix-A)." + [ "$FATAL" -ne 0 ] && exit 1 + exit 1 +fi diff --git a/tests/phase-05/fakebin/juju b/tests/phase-05/fakebin/juju new file mode 100644 index 0000000..6434f6f --- /dev/null +++ b/tests/phase-05/fakebin/juju @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# fake juju for the phase-05 verify harness. +# status ... -> $FIX_JUJU_STATUS (octavia status JSON) +# config -> env-driven value (CFG_* with sane defaults) +# exec ... o-hm0 -> $OHM0_OUT (the 'ip -br addr show o-hm0' line) +sub="${1:-}" +case "$sub" in + status) cat "${FIX_JUJU_STATUS:?FIX_JUJU_STATUS not set}" ;; + config) + app="${2:-}"; key="${3:-}" + case "$app/$key" in + octavia-diskimage-retrofit/use-internal-endpoints) printf '%s\n' "${CFG_UIE:-true}" ;; + octavia-diskimage-retrofit/image-format) printf '%s\n' "${CFG_IMGFMT:-raw}" ;; + octavia-diskimage-retrofit/amp-image-tag) printf '%s\n' "${CFG_RTAG:-octavia-amphora}" ;; + octavia/amp-image-tag) printf '%s\n' "${CFG_OTAG:-octavia-amphora}" ;; + esac ;; + exec) printf '%s\n' "${OHM0_OUT:-}" ;; +esac diff --git a/tests/phase-05/fakebin/openstack b/tests/phase-05/fakebin/openstack new file mode 100644 index 0000000..6f60a64 --- /dev/null +++ b/tests/phase-05/fakebin/openstack @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# fake openstack for the phase-05 verify harness. Replays the `-f value -c ` +# output of the list commands the verify uses; the actual --tags/--tag filters are +# baked into which fixture the harness points at. +case "${1:-} ${2:-}" in + "network list") cat "${FIX_NETS:-/dev/null}" ;; + "subnet list") cat "${FIX_SUBS:-/dev/null}" ;; + "security group") cat "${FIX_SGS:-/dev/null}" ;; # 'security group list' + "image list") cat "${FIX_IMGS:-/dev/null}" ;; +esac diff --git a/tests/phase-05/make_fixtures.py b/tests/phase-05/make_fixtures.py new file mode 100644 index 0000000..a1dd868 --- /dev/null +++ b/tests/phase-05/make_fixtures.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# tests/phase-05/make_fixtures.py OUTDIR +# Emit juju-status JSON (octavia blocked vs active) + the plain-text `-f value -c ` +# outputs the openstack shim replays for resource/image lists. +import json, os, sys + +out = sys.argv[1] +os.makedirs(out, exist_ok=True) +def wj(name, obj): + with open(os.path.join(out, name), "w") as f: json.dump(obj, f, indent=2) +def wt(name, text): + with open(os.path.join(out, name), "w") as f: f.write(text) + +def octavia_status(current, message): + return {"model": {"name": "openstack"}, + "applications": {"octavia": {"units": {"octavia/0": { + "workload-status": {"current": current, "message": message}, + "juju-status": {"current": "idle"}}}}}} + +wj("status-blocked.json", octavia_status( + "blocked", "Awaiting end-user execution of `configure-resources` action to create required resources")) +wj("status-active.json", octavia_status("active", "Unit is ready")) + +# openstack `-f value -c Name` outputs (one name per line; empty file = none) +wt("nets-empty", ""); wt("nets-present", "lb-mgmt-net\n") +wt("subs-empty", ""); wt("subs-present", "lb-mgmt-subnetv6\n") +wt("sgs-empty", ""); wt("sgs-present", "lb-mgmt-sec-grp\n") +# openstack image list `-c Status` outputs +wt("imgs-empty", ""); wt("imgs-active", "active\n") + +print("fixtures written to", out) diff --git a/tests/phase-05/run-tests.sh b/tests/phase-05/run-tests.sh new file mode 100644 index 0000000..84a0df9 --- /dev/null +++ b/tests/phase-05/run-tests.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# tests/phase-05/run-tests.sh -- offline regression for phase-05-octavia-verify.sh. +# Behavior-tests the REAL script against fake juju/openstack shims + real jq. +# No live infra. Needs python3, bash, jq. +set -euo pipefail +IFS=$'\n\t' +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPTS="$(cd "$HERE/../../scripts" && pwd)" +TARGET="$SCRIPTS/phase-05-octavia-verify.sh" +BIN="$HERE/fakebin" + +command -v python3 >/dev/null 2>&1 || { echo "FAIL: python3 required" >&2; exit 1; } +command -v jq >/dev/null 2>&1 || { echo "FAIL: jq required" >&2; exit 1; } +[ -f "$TARGET" ] || { echo "FAIL: target missing: $TARGET" >&2; exit 1; } +chmod +x "$BIN/juju" "$BIN/openstack" 2>/dev/null || true + +WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT +python3 "$HERE/make_fixtures.py" "$WORK" >/dev/null +UP_OHM0='o-hm0 UP fc00:3f8c:7162:d105:f816:3eff:feea:7e45/64' +rc_all=0 + +# run