diff --git a/overlays/vr1-dc0-vips.yaml b/overlays/vr1-dc0-vips.yaml index 1e54c37..a4926d4 100644 --- a/overlays/vr1-dc0-vips.yaml +++ b/overlays/vr1-dc0-vips.yaml @@ -33,43 +33,36 @@ applications: keystone: options: - prefer-ipv6: true vip: "10.12.4.50 10.12.8.50 10.12.12.50 2602:f3e2:f02:11::50 fd50:840e:74e2:220::50 fd50:840e:74e2:221::50" # B1 front-loaded VIP; IS the catalog endpoint (B5, no os-public-hostname) barbican: options: vip: "10.12.4.51 10.12.8.51 10.12.12.51 2602:f3e2:f02:11::51 fd50:840e:74e2:220::51 fd50:840e:74e2:221::51" # B1 cinder: options: - prefer-ipv6: true vip: "10.12.4.52 10.12.8.52 10.12.12.52 2602:f3e2:f02:11::52 fd50:840e:74e2:220::52 fd50:840e:74e2:221::52" # B1 glance: options: - prefer-ipv6: true vip: "10.12.4.53 10.12.8.53 10.12.12.53 2602:f3e2:f02:11::53 fd50:840e:74e2:220::53 fd50:840e:74e2:221::53" # B1 magnum: options: vip: "10.12.4.54 10.12.8.54 10.12.12.54 2602:f3e2:f02:11::54 fd50:840e:74e2:220::54 fd50:840e:74e2:221::54" # B1 neutron-api: options: - prefer-ipv6: true vip: "10.12.4.55 10.12.8.55 10.12.12.55 2602:f3e2:f02:11::55 fd50:840e:74e2:220::55 fd50:840e:74e2:221::55" # B1 nova-cloud-controller: options: - prefer-ipv6: true vip: "10.12.4.56 10.12.8.56 10.12.12.56 2602:f3e2:f02:11::56 fd50:840e:74e2:220::56 fd50:840e:74e2:221::56" # B1 octavia: options: vip: "10.12.4.57 10.12.8.57 10.12.12.57 2602:f3e2:f02:11::57 fd50:840e:74e2:220::57 fd50:840e:74e2:221::57" # B1 openstack-dashboard: options: - prefer-ipv6: true vip: "10.12.4.58 10.12.8.58 10.12.12.58 2602:f3e2:f02:11::58 fd50:840e:74e2:220::58 fd50:840e:74e2:221::58" # B1 -- browse HTTPS by IP (B5); ALLOWED_HOSTS must permit the VIP IP (verify at deploy) placement: options: vip: "10.12.4.59 10.12.8.59 10.12.12.59 2602:f3e2:f02:11::59 fd50:840e:74e2:220::59 fd50:840e:74e2:221::59" # B1 ceph-radosgw: options: - prefer-ipv6: true vip: "10.12.4.60 10.12.8.60 10.12.12.60 2602:f3e2:f02:11::60 fd50:840e:74e2:220::60 fd50:840e:74e2:221::60" # B1 -- radosgw HA un-deferred for Roosevelt fidelity (decorative HA on testcloud) vault: options: diff --git a/overlays/vr1-dc1-vips.yaml b/overlays/vr1-dc1-vips.yaml index 40b717f..4fdb204 100644 --- a/overlays/vr1-dc1-vips.yaml +++ b/overlays/vr1-dc1-vips.yaml @@ -33,43 +33,36 @@ applications: keystone: options: - prefer-ipv6: true vip: "10.12.64.50 10.12.68.50 10.12.72.50 2602:f3e2:f03:11::50 fd50:840e:74e2:320::50 fd50:840e:74e2:321::50" barbican: options: vip: "10.12.64.51 10.12.68.51 10.12.72.51 2602:f3e2:f03:11::51 fd50:840e:74e2:320::51 fd50:840e:74e2:321::51" cinder: options: - prefer-ipv6: true vip: "10.12.64.52 10.12.68.52 10.12.72.52 2602:f3e2:f03:11::52 fd50:840e:74e2:320::52 fd50:840e:74e2:321::52" glance: options: - prefer-ipv6: true vip: "10.12.64.53 10.12.68.53 10.12.72.53 2602:f3e2:f03:11::53 fd50:840e:74e2:320::53 fd50:840e:74e2:321::53" magnum: options: vip: "10.12.64.54 10.12.68.54 10.12.72.54 2602:f3e2:f03:11::54 fd50:840e:74e2:320::54 fd50:840e:74e2:321::54" neutron-api: options: - prefer-ipv6: true vip: "10.12.64.55 10.12.68.55 10.12.72.55 2602:f3e2:f03:11::55 fd50:840e:74e2:320::55 fd50:840e:74e2:321::55" nova-cloud-controller: options: - prefer-ipv6: true vip: "10.12.64.56 10.12.68.56 10.12.72.56 2602:f3e2:f03:11::56 fd50:840e:74e2:320::56 fd50:840e:74e2:321::56" octavia: options: vip: "10.12.64.57 10.12.68.57 10.12.72.57 2602:f3e2:f03:11::57 fd50:840e:74e2:320::57 fd50:840e:74e2:321::57" openstack-dashboard: options: - prefer-ipv6: true vip: "10.12.64.58 10.12.68.58 10.12.72.58 2602:f3e2:f03:11::58 fd50:840e:74e2:320::58 fd50:840e:74e2:321::58" placement: options: vip: "10.12.64.59 10.12.68.59 10.12.72.59 2602:f3e2:f03:11::59 fd50:840e:74e2:320::59 fd50:840e:74e2:321::59" ceph-radosgw: options: - prefer-ipv6: true vip: "10.12.64.60 10.12.68.60 10.12.72.60 2602:f3e2:f03:11::60 fd50:840e:74e2:320::60 fd50:840e:74e2:321::60" vault: options: diff --git a/scripts/provider-bundle-check.py b/scripts/provider-bundle-check.py index 1607faf..62acc2e 100644 --- a/scripts/provider-bundle-check.py +++ b/scripts/provider-bundle-check.py @@ -47,10 +47,12 @@ (`unknown option "prefer-ipv6"` on barbican) rather than ignoring it, and `juju deploy --dry-run` does NOT validate option names, so nothing else in this repo can catch it. - 9b on a charm that DOES declare it, prefer-ipv6 and the dual-family v6 legs - still travel together -- the L3-9 merge order that keeps the option while - silently dropping the v6 legs is the one that exits 0, so the dangerous - order is the GREEN one. Retained unchanged for those seven. + 9b RE-POINTED 2026-07-31 (D-101 RULING NOTE (b)): the option must be ABSENT + from EVERY application until IPv6 is operational. It previously required the + option and the v6 legs to travel together on a declaring charm; the ruling + removes it everywhere, so that form would fail on the ruled artifact. The v6 + legs are RETAINED (arity is 9c). L3-9 cannot recur while the option is absent + everywhere. This returns to the coupling form when v6 is operational. 9c arity itself: a vip is a v4 triple or a dual-family sextet, never other. PREFER_IPV6_CHARMS below is the measured authority for 9a/9b. 10. HA ARITY (2026-07-29 gate hardening, the second half of R11's shape): an hacluster @@ -398,13 +400,28 @@ # the reader to add v6 legs when the fix is to remove the option. if "prefer-ipv6" in opts and charm not in PREFER_IPV6_CHARMS: continue - # 9b -- where the option IS legal, it stays coupled to the v6 legs. Measured - # (L3-9): the overlay merge order that keeps prefer-ipv6 while dropping the v6 - # legs is the one that exits 0, so the dangerous order is the GREEN one. - if charm in PREFER_IPV6_CHARMS and prefer6 != dual: - fails.append("%s prefer-ipv6=%s but its vip carries %d address(es) -- " - "prefer-ipv6 and the v6 VIP legs must land TOGETHER" - % (n, str(prefer6).lower(), len(parts))); continue + # 9b -- RE-POINTED 2026-07-31 (D-101 RULING NOTE (b), GA-R5: "Set it false on the + # seven, keep every v6 VIP leg"). It previously required the option and the v6 legs + # to travel TOGETHER on a declaring charm (the L3-9 merge-order defect). The ruling + # removes the option from EVERY application until IPv6 is operational, so that + # coupling would now fail on the ruled artifact. The invariant is REPLACED, not + # deleted: the option must be ABSENT everywhere. + # + # Measured cause of the ruling: the LXD containers the API charms run in hold only + # a link-local v6, and `get_relation_ip()` returns early with `get_ipv6_addr()[0]` + # when the option is true -- so it raises and the install hook dies. The v6 legs + # themselves are UNAFFECTED and are retained; arity is asserted by 9c below. + # + # WHEN IPv6 BECOMES OPERATIONAL and the option is reinstated, this returns to the + # coupling form and the ruling note is the record of why it left. L3-9 cannot + # recur while the option is absent everywhere -- there is nothing to keep while + # legs are dropped. + if "prefer-ipv6" in opts: + fails.append("%s sets prefer-ipv6, which is RULED OFF for every application " + "until IPv6 is operational (D-101 RULING NOTE (b), 2026-07-31). " + "The v6 VIP legs stay; the option does not -- the LXD containers " + "hold only a link-local v6, so a charm that advertises v6 for " + "every relation dies in its install hook" % n); continue if dual and v6_bands is None and v6_refusal is None: v6_bands, v6_refusal = _apex_v6_bands(args.dc) if dual and v6_bands is None: diff --git a/scripts/render-dc-overlays.py b/scripts/render-dc-overlays.py index 858fc7d..3f68b84 100755 --- a/scripts/render-dc-overlays.py +++ b/scripts/render-dc-overlays.py @@ -297,9 +297,21 @@ # it; the octet mirror is textual, so the digits stay decimal. addrs += ["%s::%d" % (pre6[l], o) for l in legs] out.append(" %s:\n options:\n" % app["name"]) - if fam == "dual" and app["name"] in prefer6: - # RULED 2026-07-31 (D-101 RULING NOTE, GA-R5: "Yes -- keep the v6 legs, - # remove only the option"). This used to be emitted for EVERY dual-family + if False and fam == "dual" and app["name"] in prefer6: # RULED OFF -- see below + # >>> RULED OFF ENTIRELY 2026-07-31 (D-101 RULING NOTE (b), GA-R5: "Set it + # false on the seven, keep every v6 VIP leg"). The option is emitted for NO + # application until IPv6 is OPERATIONAL. Measured cause: the LXD containers the + # API charms run in hold only a link-local v6, so `get_relation_ip()` -- which + # returns early with `get_ipv6_addr()[0]` when this is true -- raises and the + # install hook dies. The condition is left in place, disabled, rather than + # deleted: when the v6 completion work lands (allocatable ranges on the six v6 + # plane subnets, rack-side v6 legs, a v6 default route, v6-reachable services) + # this is the one line that turns it back on, and PREFER_IPV6_CHARMS is still + # the correct set to gate it by. + # + # Original 2026-07-31 (a) rationale, still true and still the reason the SIX + # never get it: RULED (GA-R5: "Yes -- keep the v6 legs, remove only the + # option"). This used to be emitted for EVERY dual-family # app, on the belief that prefer-ipv6 is what makes HAProxy bind :::port. # MEASURED FALSE: that bind is gated on `ipv6_enabled = not # is_ipv6_disabled()`, a read of the kernel disable_ipv6 sysctl, and diff --git a/tests/provider-bundle-check/run-tests.sh b/tests/provider-bundle-check/run-tests.sh index 6d58d69..72572fa 100644 --- a/tests/provider-bundle-check/run-tests.sh +++ b/tests/provider-bundle-check/run-tests.sh @@ -233,31 +233,37 @@ run 0 '13 clustered VIP\(s\).*13 dual-family' \ "T19 every app is dual-family in the real deploy input" "$TMP/good.yaml" -# T20 prefer-ipv6 kept while the v6 legs are dropped -- the L3-9 merge order that -# exits 0 today. This is the whole reason the coupling exists. -mutate t20.yaml 'o=b["applications"]["keystone"]["options"]; o["vip"]=" ".join(o["vip"].split()[:3])' -run 1 'prefer-ipv6 and the v6 VIP legs must land TOGETHER' \ - "T20 prefer-ipv6 without v6 legs FAILS" "$TMP/t20.yaml" +# T20/T21 RE-POINTED 2026-07-31 (D-101 RULING NOTE (b), operator: "Set it false on the +# seven, keep every v6 VIP leg"). Both previously asserted the COUPLING -- that the +# option and the v6 legs travel together on a declaring charm. The ruling removes the +# option from EVERY application until IPv6 is operational, so the coupling form would +# fail on the ruled artifact. The assertions are RE-POINTED to the new invariant, not +# deleted, and both subjects are kept: keystone is a DECLARING charm, which is exactly +# the case the old coupling covered and the case most likely to be "restored" by +# someone who remembers R2. +# T20 the option on a declaring charm now FAILS, v6 legs or not. +mutate t20.yaml 'b["applications"]["keystone"]["options"]["prefer-ipv6"]=True' +run 1 'RULED OFF for every application' \ + "T20 prefer-ipv6 on a DECLARING charm FAILS (ruled off everywhere)" "$TMP/t20.yaml" -# T21 the inverse. RATIONALE RE-POINTED 2026-07-31, assertion UNCHANGED: this used to -# read "v6 legs present but nothing binds v6", which is refuted -- the ':::port' -# bind is gated on the kernel disable_ipv6 sysctl, not on this option -# (docs/audit/stage5-prefer-ipv6-charm-research-20260731.txt). It still FAILS, and -# should: on a charm that DECLARES the option, R2 has it set, and a merge that -# silently drops it is the same undetected-divergence class T20 covers. keystone is -# deliberately the subject here -- it is one of the SEVEN, so invariant 9b applies. -mutate t21.yaml 'b["applications"]["keystone"]["options"].pop("prefer-ipv6")' -run 1 'prefer-ipv6 and the v6 VIP legs must land TOGETHER' \ - "T21 v6 legs without prefer-ipv6 FAILS (declaring charm)" "$TMP/t21.yaml" +# T21 the ruled shape -- v6 legs present, option absent -- must PASS. This is the +# positive control the absence rule needs: without it, 9b could be satisfied by +# rejecting everything. +run 0 '13 clustered VIP\(s\).*13 dual-family' \ + "T21 v6 legs WITHOUT the option PASSES (the ruled shape)" "$TMP/good.yaml" # T22 v6 provider leg in the NODE /64 instead of the dedicated GUA VIP /64 -mutate t22.yaml "b['applications']['keystone']['options'].update({'vip':'${DUAL6/f02:11::50/f02:10::50}','prefer-ipv6':True})" +# NOTE: these no longer set prefer-ipv6. They did, because the OLD coupling required +# it alongside v6 legs; under the 2026-07-31 (b) absence rule 9b fires FIRST and +# short-circuits, so the v6-band assertion would never be reached and the case +# would pass for the wrong reason. +mutate t22.yaml "b['applications']['keystone']['options'].update({'vip':'${DUAL6/f02:11::50/f02:10::50}'})" run 1 'provider v6 leg .* not in 2602:f3e2:f02:11::/64' \ "T22 v6 leg in the wrong /64 FAILS" "$TMP/t22.yaml" # T23 v6 host part not mirroring the v4 octet -- `printf '%x' 50` = 32 is the exact # plausible-looking wrong value the 2026-07-27 ruling exists to prevent. -mutate t23.yaml "b['applications']['keystone']['options'].update({'vip':'${DUAL6/f02:11::50/f02:11::32}','prefer-ipv6':True})" +mutate t23.yaml "b['applications']['keystone']['options'].update({'vip':'${DUAL6/f02:11::50/f02:11::32}'})" run 1 'must MIRROR the v4 octet' "T23 v6 host part not mirroring the v4 octet FAILS" "$TMP/t23.yaml" # T24 arity that is neither a triple nor a sextet @@ -311,11 +317,13 @@ # T30 the v6 analogue of T15: dc0 v6 legs under --dc vr1-dc1 must FAIL, so a # cross-DC copy-paste of a rendered overlay cannot pass. +# The fixture no longer carries prefer-ipv6 -- same reason as T22/T23: under the +# 2026-07-31 (b) absence rule 9b fires first and the cross-DC band assertion would +# never be reached. cat > "$TMP/dc1-dual-wrong.yaml" <<'YAML' applications: keystone: options: - prefer-ipv6: true vip: "10.12.64.50 10.12.68.50 10.12.72.50 2602:f3e2:f02:11::50 fd50:840e:74e2:220::50 fd50:840e:74e2:221::50" YAML runargs 1 'provider v6 leg .* not in 2602:f3e2:f03:11::/64' \