# ============================================================
# Caracal 2024.1 -- VR0 DC0 Omega Cloud testcloud rebuild bundle
# ============================================================
# Generated:        2026-05-22 (rebuild revision 2026-06-01, bundle-cleanup change-set)
# Replaces:         bundle-pre-destroy.yaml (Bobcat 2023.2)
# Charm channels:   verified against Charmhub 2026-05-22 (see Caracal_Rebuild handoff D-002)
# Bindings:         public:provider, else:metal for API charms; all-metal for backend charms.
#                   Ceph data nets via public/cluster BINDINGS on ceph-mon/ceph-osd (these provision the
#                   container/host NICs; ceph-*-network config would NOT). Ceph CLIENTS bind ceph->storage,
#                   and each subordinate's storage/data binding is mirrored on its PRINCIPAL (subset rule). (C2)
# Endpoints:        IP-ONLY -- os-public-hostname dropped on all API charms; the dual VIPs ARE the
#                   catalog endpoints (public 10.12.4.N / internal+admin 10.12.8.N). Vault issues
#                   per-VIP IP-SAN certs. No control-plane DNS dependency. (B5)
#                   VR1 DC-DC NOTE (2026-07-10, DOCFIX-167): D-106 reactivates os-public-hostname
#                   PER-DC via a separate overlay (overlays/${DC}-hostnames.yaml, proposed in
#                   runbooks/dc-dc-phase6-designate-cos-magnum.md) once Designate exists -- this base
#                   bundle stays DC-agnostic; B5's IP-only posture is this file's own DEFAULT, only
#                   overridden per-DC via that overlay, not edited here.
# HA chain:         hacluster subordinates + :ha relations ACTIVE for 12 charms (10 v1 API charms +
#                   ceph-radosgw, un-deferred, + designate, reactivated 2026-07-10 per D-106) -- of
#                   these, 11 carry the dual-VIP pattern (designate uses its own dnsaas/DNS-native
#                   addressing, not an HAProxy VIP, so it doesn't add to the VIP-triple count). VIPs
#                   front-loaded into the MAAS-reserved /26: provider 10.12.4.2-.63, metal
#                   10.12.8.2-.63 (supersedes .224-.254). (B1)
# Vault:            single unit, MYSQL storage backend (via vault-mysql-router). etcd + easyrsa
#                   REMOVED -- the etcd backend was never used (live storage = mysql) and is moot at
#                   1 unit; HA backend (Raft vs etcd) is a Roosevelt rehearsal item. (C1; revises D-006)
# Ceph networks:    FULL separation via network-space BINDINGS -- ceph-mon/ceph-osd public->storage
#                   (10.12.32.0/22), ceph-osd cluster->replication (10.12.36.0/22). Bindings, NOT
#                   ceph-*-network config, so the LXD-contained mon actually gets a storage NIC.
#                   Clients bind ceph->storage; container principals carry it too (subset rule). (C2)
# Magnum:           Layer A only -- CAPI driver graft is Layer B (runbooks/phase-06..08)
# Octavia:          lb-mgmt PKI options supplied via overlays/octavia-pki.yaml (gitignored).
#                   Amphora-pipeline options baked (use-internal-endpoints etc.). (B4)
# OVN tunnels:      geneve overlay on the DATA space (10.12.12.0/22) -- ovn-chassis + ovn-chassis-octavia
#                   'data' binding; their principals also carry data (nova-compute:neutron-plugin bare-metal,
#                   octavia:ovsdb-cms provisions the container NIC) per the subset rule. Prereq: enp8s0
#                   link-subnet to 10.12.12.4N (rebuild-prep, machines Ready).
# Resources:        omitted -- let charms use latest available resource revisions
# ============================================================

name: vr0-dc0-omega-caracal-testcloud
description: |
  Charmed OpenStack Caracal (2024.1) on Ubuntu 22.04 LTS (Jammy) deployed via Juju 3.6 bundle
  against MAAS-managed VMs (openstack0-3, virsh).
  Decisions referenced (see Caracal_Rebuild handoff + 2026-06-01 bundle-cleanup change-set):
    D-001 Path 2A (Juju-bundle paradigm)
    D-002 channel matrix
    D-003 Option B (provider /22 carries FIPs + API VIPs)
    D-005 Ceph Squid
    D-006 Vault HA backend -- REVISED: etcd/easyrsa dropped for testcloud; Raft-vs-etcd is a Roosevelt item (C1)
    D-007 Magnum Layer A + Layer B graft
    D-019 (supersedes D-008) Designate deferred to v2 -- SUPERSEDED 2026-07-10 by D-106
      (VR1 DC-DC reactivation; see designate/designate-bind/designate-hacluster below and
      docs/changelog-20260709-designate-cinderbackup-rbdmirror.md, DOCFIX-167). D-019's
      text is retained for history per this repo's append-only decision discipline -- the
      charms below are the concrete reversal.
    D-009 hacluster subordinates (decorative on testcloud)
    D-016 IPv4-only v1
    D-018 MAAS-release-direct teardown
  Bundle-cleanup (2026-06-01): B5 IP-only endpoints; C1 vault-on-mysql (etcd/easyrsa removed);
    C2 full Ceph network separation; B1 VIP front-load + radosgw HA un-defer; B2 ovn prefer-chassis-as-gw;
    B3 nova Ceph-RBD ephemeral; B4 octavia amphora-pipeline options. C3 radosgw unchanged (already correct).

default-base: ubuntu@22.04/stable

variables:
  # ----- UCA pocket + Ceph source ----------------------------------------------
  openstack-origin: &openstack-origin cloud:jammy-caracal
  ceph-source: &ceph-source cloud:jammy-caracal

  # ----- Bindings for external-API-facing charms (public on provider) ----------
machines:
  "8":
    constraints: arch=amd64 tags=openstack
  "9":
    constraints: arch=amd64 tags=openstack
  "10":
    constraints: arch=amd64 tags=openstack
  "11":
    constraints: arch=amd64 tags=openstack

# =====================================================================
# Network-space bindings (D-052): EXPLICIT per-application blocks, no anchors.
#   ""             -> metal-admin     (operator/MAAS/monitoring; admin API; default)
#   internal/shared-db/amqp/certificates/cluster/identity/ovsdb -> metal-internal
#   public         -> provider-public (public API + floating IPs)
#   ceph public    -> storage    ;  ceph cluster -> replication
#   geneve overlay -> data-tenant (nova-compute:neutron-plugin, ovn-chassis:data,
#                                  ovn-chassis-octavia:data, octavia:ovsdb-cms)
# Subordinate subset rule: a subordinate's spaces are a subset of its principal's;
#   nova-compute keeps data-tenant (via neutron-plugin) for the ovn-chassis geneve.
# Re-IP is MAAS-side only (no CIDR options here). See docs/design-decisions.md D-052.
# =====================================================================
applications:

  # =====================================================================
  # Datastores: MySQL InnoDB Cluster, RabbitMQ, Vault
  # =====================================================================
  # C1: etcd + easyrsa REMOVED. Vault is single-unit and uses the MySQL storage backend via
  # vault-mysql-router (matches the live deploy; the etcd HA backend was never exercised and is
  # moot at one unit). Vault HA backend (Raft vs etcd) is a Roosevelt rehearsal item.

  mysql-innodb-cluster:
    charm: mysql-innodb-cluster
    channel: 8.0/stable
    num_units: 3
    to: [lxd:8, lxd:9, lxd:10]
    bindings:
      '': metal-admin
      certificates: metal-internal
      cluster: metal-internal
      coordinator: metal-internal
      db-router: metal-internal
      shared-db: metal-internal
    constraints: arch=amd64

  rabbitmq-server:
    charm: rabbitmq-server
    channel: 3.9/stable
    num_units: 1
    to: [lxd:10]
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      cluster: metal-internal
      ha: metal-internal
    constraints: arch=amd64

  vault:
    charm: vault
    # D-068 AMENDMENT (2026-07-05): reverted 1.16/stable -> 1.8/stable. The 1.16 line is a
    # fundamentally different charm (certs interface V0->V1, Raft-only storage, no upgrade
    # path, broken with Charmed Ceph) -- INCOMPATIBLE with this reactive cloud; a deployable
    # bundle MUST use 1.8/stable. 1.8.8 is EOL (tech debt); getting off it is an OPEN D-068
    # problem with 1.16 ruled out. See docs/D-068-vault-1.8-vs-1.16-analysis.md. BUNDLEFIX-010.
    channel: 1.8/stable
    num_units: 1                       # 3 on Roosevelt (D-009); HA backend decided there (C1)
    to: [lxd:11]
    bindings:
      '': metal-admin
      access: metal-internal
      certificates: metal-internal
      cluster: metal-internal
      # BUNDLEFIX-007 (D-067 amendment, 2026-07-02): external MUST equal access. The charm computes
      # vault_url (access) AND vault_url_external (external) and, because the vault-kv interface
      # ignores remote_binding (deprecated, LP#1895185), the external publish CLOBBERS vault_url on
      # ALL kv consumer relations. If external falls to the '' default (metal-admin), every consumer
      # (barbican-vault) is told to dial the metal-admin address while its AppRole secret_id is
      # CIDR-bound to its metal-internal /32 -> deterministic login reject -> Barbican 500.
      # Present on charm-vault stable/1.8 AND master (checked 2026-07-02) -- keep this line on upgrades.
      external: metal-internal
      ha: metal-internal
      secrets: metal-internal
      shared-db: metal-internal
    constraints: arch=amd64

  vault-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  # =====================================================================
  # Identity: Keystone
  # =====================================================================

  keystone:
    # DOCFIX-071: the D-051/D-064 SCS Domain Manager policy ships WITH the deploy --
    # a manual post-deploy attach-resource proved unreachable from the schedule.
    # Keep policies/overrides.zip in sync with domain-manager-policy.yaml
    # (provider-bundle-check.py enforces the content match).
    resources:
      policyd-override: ./policies/overrides.zip
    charm: keystone
    channel: 2024.1/stable
    num_units: 1                       # 3 on Roosevelt (D-009)
    to: [lxd:8]
    options:
      vip: "10.12.4.50 10.12.8.50 10.12.12.50" # B1 front-loaded VIP; IS the catalog endpoint (B5, no os-public-hostname)
      use-policyd-override: true       # as-built reconcile 2026-06-09 (origin untraced -- Review-later)
    bindings:
      '': metal-admin
      certificates: metal-internal
      cluster: metal-internal
      domain-backend: metal-internal
      ha: metal-internal
      identity-admin: metal-internal
      identity-credentials: metal-internal
      identity-notifications: metal-internal
      identity-service: metal-internal
      internal: metal-internal
      keystone-fid-service-provider: metal-internal
      keystone-middleware: metal-internal
      public: provider-public
      shared-db: metal-internal
      websso-trusted-dashboard: metal-internal
    constraints: arch=amd64

  keystone-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  # =====================================================================
  # Image: Glance + simplestreams-sync
  # =====================================================================

  glance:
    charm: glance
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:11]
    options:
      vip: "10.12.4.53 10.12.8.53 10.12.12.53" # B1
      image-conversion: true           # as-built; image conversion enabled (raw on Ceph-backed glance)
    bindings:
      '': metal-admin
      amqp: metal-internal
      ceph: storage
      certificates: metal-internal
      cinder-volume-service: metal-internal
      cluster: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      image-service: metal-internal
      internal: metal-internal
      object-store: metal-internal
      public: provider-public
      shared-db: metal-internal
      storage-backend: metal-internal
    constraints: arch=amd64

  glance-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  glance-simplestreams-sync:
    charm: glance-simplestreams-sync
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:8]
    options:                           # B4 amphora-pipeline
      use-internal-endpoints: true     # use internal (IP) catalog endpoints
      use_swift: false                 # skip swift index; sidesteps radosgw object path for the amphora seed
    bindings:
      '': metal-admin
      certificates: metal-internal
      identity-service: metal-internal
      image-modifier: metal-internal
      simplestreams-image-service: metal-internal
    constraints: arch=amd64

  # =====================================================================
  # Compute: Nova cloud-controller + compute + Placement
  # =====================================================================

  nova-cloud-controller:
    charm: nova-cloud-controller
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:11]
    options:
      console-access-protocol: novnc
      network-manager: Neutron
      vip: "10.12.4.56 10.12.8.56 10.12.12.56" # B1
    bindings:
      '': metal-admin
      amqp: metal-internal
      amqp-cell: metal-internal
      certificates: metal-internal
      cinder-volume-service: metal-internal
      cloud-compute: metal-internal
      cloud-controller: metal-internal
      cluster: metal-internal
      dashboard: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      image-service: metal-internal
      internal: metal-internal
      memcache: metal-internal
      neutron-api: metal-internal
      nova-cell-api: metal-internal
      placement: metal-internal
      public: provider-public
      shared-db: metal-internal
      shared-db-cell: metal-internal
    constraints: arch=amd64

  nova-compute:
    charm: nova-compute
    channel: 2024.1/stable
    num_units: 3
    to: ["9", "10", "11"]
    options:
      config-flags: default_ephemeral_format=ext4
      enable-live-migration: true      # now genuinely usable -- shared Ceph storage = memory-only migrate (B3)
      enable-resize: true
      libvirt-image-backend: rbd       # B3 Ceph-RBD ephemeral: DISK_GB from the Ceph pool, not local fs; unlocks Magnum
      migration-auth-type: ssh
      resume-guests-state-on-host-boot: true
      virt-type: qemu                  # Testcloud nested-KVM; Roosevelt will use 'kvm'
      reserved-host-memory: 8192       # ENV(testcloud 16GiB hosts) D-040 OOM fix; charm default 512 -- DO NOT drop
    bindings:
      '': metal-admin
      amqp: metal-internal
      ceph: storage
      ceph-access: storage
      cloud-compute: metal-internal
      cloud-credentials: metal-internal
      compute-peer: metal-internal
      image-service: metal-internal
      internal: metal-internal
      migration: metal-internal
      neutron-plugin: data-tenant
      secrets-storage: metal-internal
      storage-backend: metal-internal
    constraints: arch=amd64

  ncc-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  placement:
    charm: placement
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:11]
    options:
      vip: "10.12.4.59 10.12.8.59 10.12.12.59" # B1
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      cluster: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      internal: metal-internal
      placement: metal-internal
      public: provider-public
      shared-db: metal-internal
    constraints: arch=amd64

  placement-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  # =====================================================================
  # Networking: Neutron + OVN
  # =====================================================================

  neutron-api:
    charm: neutron-api
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:9]
    options:
      enable-ml2-port-security: true
      flat-network-providers: physnet1
      neutron-security-groups: true
      vip: "10.12.4.55 10.12.8.55 10.12.12.55" # B1
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      cluster: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      internal: metal-internal
      neutron-api: metal-internal
      neutron-plugin-api: metal-internal
      neutron-plugin-api-subordinate: metal-internal
      public: provider-public
      shared-db: metal-internal
    constraints: arch=amd64

  neutron-api-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  neutron-api-plugin-ovn:
    charm: neutron-api-plugin-ovn
    channel: 2024.1/stable

    bindings:
      '': metal-admin
      certificates: metal-internal
      neutron-plugin: metal-internal
      ovsdb-cms: metal-internal
  ovn-central:
    charm: ovn-central
    channel: 24.03/stable
    num_units: 3
    to: [lxd:8, lxd:9, lxd:10]
    bindings:
      '': metal-admin
      certificates: metal-internal
      coordinator: metal-internal
      ovsdb: metal-internal
      ovsdb-cms: metal-internal
      ovsdb-peer: metal-internal
      ovsdb-server: metal-internal
    constraints: arch=amd64

  # ovn-chassis: subordinate to nova-compute. MAC-based bridge-interface-mappings captured from
  # MAAS 2026-05-22 (Bobcat used hardcoded 'enp1s0' -- anti-pattern fix). The charm picks whichever
  # MAC is found locally per unit; non-matching MACs are ignored.
  ovn-chassis:
    charm: ovn-chassis
    channel: 24.03/stable
    options:
      ovn-bridge-mappings: physnet1:br-ex
      prefer-chassis-as-gw: true       # B2 -- elects gateway chassis so tenant routers get external egress
      bridge-interface-mappings: >-
        br-ex:52:54:00:3d:fd:54
        br-ex:52:54:00:9d:63:77
        br-ex:52:54:00:89:7f:ce
        br-ex:52:54:00:99:fc:c2
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      data: data-tenant
      ovsdb: metal-internal
      ovsdb-subordinate: metal-internal
  ovn-chassis-octavia:
    charm: ovn-chassis
    channel: 24.03/stable
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      data: data-tenant
      ovsdb: metal-internal
      ovsdb-subordinate: metal-internal
  cinder:
    charm: cinder
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:9]
    options:
      block-device: None
      glance-api-version: 2
      vip: "10.12.4.52 10.12.8.52 10.12.12.52" # B1
    bindings:
      '': metal-admin
      amqp: metal-internal
      backup-backend: metal-internal
      ceph: storage
      certificates: metal-internal
      cinder-volume-service: metal-internal
      cluster: metal-internal
      ha: metal-internal
      identity-credentials: metal-internal
      identity-service: metal-internal
      image-service: metal-internal
      internal: metal-internal
      public: provider-public
      shared-db: metal-internal
      storage-backend: metal-internal
    constraints: arch=amd64            #   owns the relation -- but the binding still provisions the NIC.

  cinder-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  cinder-ceph:
    charm: cinder-ceph
    channel: 2024.1/stable
    bindings:
      '': metal-admin
      ceph: storage
      ceph-access: storage
      storage-backend: metal-internal
  ceph-mon:
    charm: ceph-mon
    channel: squid/stable
    num_units: 3
    to: [lxd:8, lxd:9, lxd:10]
    options:
      source: *ceph-source
      expected-osd-count: 4
      monitor-count: 3
    bindings:
      '': metal-admin
      bootstrap-source: storage
      client: storage
      cluster: replication
      mds: storage
      mon: storage
      osd: storage
      public: storage
      radosgw: storage
      rbd-mirror: replication          # corrected 2026-07-10 (D-108, DOCFIX-167): was
                                       #   `storage`, inherited unchanged from the VR0/DC0 seed
                                       #   (predates D-108) -- cross-DC rbd-mirror daemon traffic
                                       #   must ride the IPv6-only ULA replication plane, not the
                                       #   intra-DC OSD storage plane.
    constraints: arch=amd64            #   provisions the NIC and sets the Ceph public net. Mons use only the
                                       #   public net (no cluster binding needed).

  ceph-osd:
    charm: ceph-osd
    channel: squid/stable
    num_units: 4
    to: ["8", "9", "10", "11"]
    options:
      source: *ceph-source
      osd-devices: /dev/vdb            # libvirt-attached, MAAS-untracked, wiped pre-deploy
    bindings:
      '': metal-admin
      cluster: replication
      mon: storage
      public: storage
      secrets-storage: metal-internal
    constraints: arch=amd64 tags=openstack

  ceph-radosgw:
    charm: ceph-radosgw
    channel: squid/stable
    num_units: 1
    to: [lxd:8]
    options:
      source: *ceph-source
      vip: "10.12.4.60 10.12.8.60 10.12.12.60" # B1 -- radosgw HA un-deferred for Roosevelt fidelity (decorative HA on testcloud)
    bindings:
      '': metal-admin
      certificates: metal-internal
      cluster: metal-internal
      gateway: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      internal: metal-internal
      mon: storage
      object-store: metal-internal
      public: provider-public
      radosgw-user: metal-internal
      s3: metal-internal
    constraints: arch=amd64

  # =====================================================================
  # Dashboard: openstack-dashboard (Horizon)
  # =====================================================================

  openstack-dashboard:
    charm: openstack-dashboard
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:10]
    options:
      debug: "false"
      vip: "10.12.4.58 10.12.8.58 10.12.12.58" # B1 -- browse HTTPS by IP (B5); ALLOWED_HOSTS must permit the VIP IP (verify at deploy)
    bindings:
      '': metal-admin
      application-dashboard: metal-internal
      certificates: metal-internal
      # BUNDLEFIX-011 / D-072 (2026-07-05): cluster on metal-admin, NOT metal-internal.
      # The charm renders haproxy's 443 backend on the cluster-binding address but only
      # creates apache SSL vhosts for the default-binding + public-binding addresses --
      # cluster on metal-internal = TLS backend on a vhost-less address = VIP https dead
      # (L4 check masks it). metal-admin matches the default binding so the backend
      # lands on a served vhost. Do NOT use provider-public (tenant-facing plane).
      cluster: metal-admin
      dashboard: metal-internal
      dashboard-plugin: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      public: provider-public
      shared-db: metal-internal
      website: metal-internal
      websso-fid-service-provider: metal-internal
      websso-trusted-dashboard: metal-internal
    constraints: arch=amd64

  dashboard-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  # =====================================================================
  # Load Balancer: Octavia
  # =====================================================================
  # CRITICAL: vault:certificates must be in bundle from day-one (post-deploy add causes the
  # documented apache2/octavia-api masking bug -- see test deployment v3 handoff).

  octavia:
    charm: octavia
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:11]
    options:
      debug: false
      openstack-origin: *openstack-origin
      amp-image-tag: octavia-amphora   # B4 -- MUST match the tag octavia-diskimage-retrofit stamps
      # ----- PKI material -------------------------------------------------
      # 5 lb-mgmt-* options are supplied via overlays/octavia-pki.yaml
      # (gitignored). Generated per runbooks/01a-octavia-pki-generation.md.
      # Deploy with:
      #   juju deploy ./bundle.yaml \
      #     --overlay overlays/vr0-dc0-testcloud.yaml \
      #     --overlay overlays/octavia-pki.yaml
      vip: "10.12.4.57 10.12.8.57 10.12.12.57" # B1
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      cluster: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      internal: metal-internal
      neutron-api: metal-internal
      neutron-openvswitch: metal-internal
      ovsdb-cms: data-tenant
      ovsdb-subordinate: metal-internal
      public: provider-public
      shared-db: metal-internal
    constraints: arch=amd64            #   subset for the subordinate's data binding (subset rule).

  octavia-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  octavia-dashboard:
    charm: octavia-dashboard
    channel: 2024.1/stable

    bindings:
      '': metal-admin
      certificates: metal-internal
      dashboard: metal-internal
  octavia-diskimage-retrofit:
    charm: octavia-diskimage-retrofit
    channel: 2024.1/stable
    options:
      amp-image-tag: octavia-amphora
      use-internal-endpoints: true     # B4 -- charm ships FALSE; required so the retrofit glance client uses the internal (IP) endpoint
      image-format: raw                # B4 -- RAW, not the qcow2 default: glance is Ceph-backed, and the charm
                                       #   + Ceph docs recommend raw so RBD can fast-clone the amphora (qcow2
                                       #   forces a convert-on-import and defeats CoW).
    bindings:
      '': metal-admin
      certificates: metal-internal
      identity-credentials: metal-internal

  # =====================================================================
  # Secrets: Barbican
  # =====================================================================
  # FIXED 2026-07-10 (full-project sweep): this section header comment
  # previously sat BEFORE octavia-diskimage-retrofit's own trailing bindings
  # block above (a cosmetic misplacement, not a functional bug -- YAML always
  # attached that block to octavia-diskimage-retrofit correctly regardless of
  # where the comment sat). Moved here, immediately before barbican itself,
  # to stop it visually implying the bindings above belong to Barbican.

  barbican:
    charm: barbican
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:11]
    options:
      openstack-origin: *openstack-origin
      vip: "10.12.4.51 10.12.8.51 10.12.12.51" # B1
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      cluster: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      internal: metal-internal
      public: provider-public
      secrets: metal-internal
      shared-db: metal-internal
    constraints: arch=amd64

  barbican-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  barbican-vault:
    charm: barbican-vault
    channel: 2024.1/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      secrets: metal-internal
      secrets-storage: metal-internal

  # =====================================================================
  # Kubernetes-as-a-Service: Magnum (Layer A -- CAPI graft is Layer B)
  # =====================================================================
  # NOTE: After bundle deploys, magnum/0 will show active/idle but CANNOT create K8s clusters.
  # Layer B (post-deploy) brings it to life -- see runbooks/phase-06..08:
  #   1. In-cloud single-homed mgmt VM (capi-mgmt-v2) with k8s-snap + CAPI/CAPO  (phase-06; D-035)
  #   2. magnum-capi-helm==1.4.0 grafted onto the conductor                      (phase-07; D-037/D-042)
  #   3. /etc/magnum/magnum.conf.d/00-capi-helm.conf (driver) + 50-keystone-v3-override.conf,
  #      both read via --config-dir wired into /etc/default/magnum-{conductor,api}  (D-037/D-047)
  #   4. kubeconfig at /etc/magnum/kubeconfig (server = the mgmt FIP)             (phase-07)
  #   5. magnum trustee domain-setup (REQUIRED; D-046); per-cluster app-creds are
  #      minted by magnum at cluster-create -- NO static capo user/app-cred       (D-039)
  # FIXED 2026-07-10 (full-project sweep): this section header, and its NOTE,
  # previously sat BEFORE barbican-vault's own trailing bindings block above (a
  # cosmetic misplacement -- YAML always attached that block to barbican-vault
  # correctly regardless of comment position). Moved here, immediately before
  # magnum itself.

  magnum:
    charm: magnum
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:9]
    options:
      openstack-origin: *openstack-origin
      region: RegionOne
      vip: "10.12.4.54 10.12.8.54 10.12.12.54" # B1
    bindings:
      '': metal-admin
      amqp: metal-internal
      certificates: metal-internal
      cluster: metal-internal
      ha: metal-internal
      identity-service: metal-internal
      internal: metal-internal
      public: provider-public
      shared-db: metal-internal
    constraints: arch=amd64

  magnum-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  magnum-dashboard:
    charm: magnum-dashboard
    channel: 2024.1/stable
    bindings:
      '': metal-admin
      dashboard: metal-internal
    # FIXED 2026-07-10 (full-project sweep): this app previously had NO bindings
    # block at all -- a pre-existing gap (present since the D-052 explicit-bindings
    # commit, not introduced tonight), violating this bundle's own stated
    # discipline ("explicit per-application blocks, no anchors"). Confirmed via
    # the real charm-magnum-dashboard metadata.yaml: subordinate, ONLY a `dashboard`
    # (interface dashboard-plugin) requires endpoint -- no `certificates` endpoint
    # exists on this charm (unlike octavia-dashboard, its closest sibling in this
    # bundle, which does have one), so this block is deliberately minimal, not a
    # partial copy of octavia-dashboard's shape.

  # =====================================================================
  # Designate (DNS) -- REACTIVATED 2026-07-10 per D-106 (supersedes D-019's
  # v1 deferral, reactivates the original D-008 bootstrap order). Backend
  # designate-bind (bind9, lowest-delta Charmed-OpenStack-native backend per
  # D-106's own text). Per-DC INDEPENDENT zones once deployed (independent
  # Keystones) -- the zone name/A/AAAA population itself is NOT part of this
  # bundle; it is Stage 7's own runbook (runbooks/dc-dc-phase6-designate-cos-
  # magnum.md), which also proposes the per-DC os-public-hostname overlay
  # this reactivation's other coupled change (B5 reversal) depends on.
  # Channel matches this bundle's existing Caracal 2024.1 pin (D-002 matrix).
  # NOT YET DEPLOYED to any live model -- prep-only repo change, 2026-07-10.
  # =====================================================================
  designate:
    charm: designate
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:9]
    bindings:
      '': metal-admin
      dns-backend: metal-internal
      identity-service: metal-internal
      shared-db: metal-internal
      certificates: metal-internal
      coordinator-memcached: metal-internal
      dnsaas: provider-public       # designate's REAL provides-side endpoint name
                                    # (interface "designate") -- confirmed via its
                                    # own metadata.yaml; NOT named "public" like the
                                    # other API charms in this bundle.
      ha: metal-internal
    constraints: arch=amd64

  designate-mysql-router:
    charm: mysql-router
    channel: 8.0/stable
    bindings:
      '': metal-admin
      certificates: metal-internal
      db-router: metal-internal
      shared-db: metal-internal

  designate-bind:
    charm: designate-bind
    channel: 2024.1/stable
    num_units: 1
    to: [lxd:9]
    bindings:
      '': metal-admin
      dns-backend: metal-internal
    constraints: arch=amd64

  # =====================================================================
  # Cinder-backup + Ceph rbd-mirror -- D-108 cross-DC replication mechanism
  # (VR1 DC-DC, added 2026-07-10; tooling gap register item #14). cinder's
  # own `backup-backend` binding already existed (metal-internal, unused
  # until now); ceph-mon's `rbd-mirror` binding is corrected below from
  # `storage` to `replication` in the SAME delivery (D-108 requires the
  # cross-DC rbd-mirror daemon traffic ride the IPv6-only ULA replication
  # plane, not the intra-DC OSD storage plane -- the old binding predates
  # D-108, inherited unchanged from the VR0/DC0 seed at commit 8813efc).
  # `cinder-backup` is a SUBORDINATE charm (confirmed: real metadata.yaml
  # carries `subordinate: true`, provides ONLY `backup-backend` (interface
  # `cinder-backup`, scope: container) + requires `ceph`/`juju-info` -- no
  # amqp relation exists on this charm, unlike a principal service) -- no
  # num_units/to/constraints, it colocates with `cinder` automatically,
  # matching this bundle's existing `cinder-ceph` subordinate shape.
  # `ceph-rbd-mirror` IS a principal (confirmed: real metadata.yaml carries
  # `subordinate: false`), with TWO requires endpoints on the SAME interface
  # (`ceph-rbd-mirror`): `ceph-local` (this DC's own ceph-mon, wired below)
  # and `ceph-remote` (the PEER DC's ceph-mon) -- `ceph-remote` is a
  # cross-DC, cross-Juju-controller relation (D-104: one independent
  # controller per DC) and CANNOT be expressed as a same-bundle relation
  # line; it needs either a `juju offer`/`juju consume` cross-model relation
  # or (the mechanism `runbooks/dc-dc-phase5-dr-failover-drill.md` already
  # documents) the manual `rbd mirror pool peer bootstrap create/import`
  # CLI flow -- NOT wired here, flagged rather than invented.
  # Single-unit for the principal, matching D-108/D-104's VR1 test economy
  # (daemon HA is single-unit for VR1, noted for Roosevelt scale-up).
  # NOT YET DEPLOYED to any live model -- prep-only repo change, 2026-07-10.
  # =====================================================================
  cinder-backup:
    charm: cinder-backup
    channel: 2024.1/stable
    bindings:
      '': metal-admin
      ceph: storage

  ceph-rbd-mirror:
    charm: ceph-rbd-mirror
    channel: squid/stable
    num_units: 1
    to: [lxd:10]
    bindings:
      '': metal-admin
      ceph-local: replication
      ceph-remote: replication   # this DC's own leg only -- see note above; the peer DC's side is NOT this bundle's concern
      nrpe-external-master: metal-internal
    constraints: arch=amd64

  # =====================================================================
  # HA Cluster Subordinates (12 active for VR1: 10 v1 API charms + ceph-radosgw
  # + designate, reactivated 2026-07-10 per D-106 -- see designate-hacluster below)
  # =====================================================================
  # Channel: 2.4/stable (per Caracal Charm Delivery table, D-002 verified 2026-05-22).
  # cluster_count: 1 (decorative on single-unit testcloud, D-009 / BUNDLEFIX-003).
  # VIPs front-loaded into the MAAS-reserved provider/metal /26 per B1 (.2-.63).
  # vault-hacluster stays commented (vault single-unit on mysql, C1 / BUNDLEFIX-002).
  #
    bindings:
      '': metal-admin
      certificates: metal-internal
      dashboard: metal-internal
  keystone-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  glance-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  neutron-api-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  nova-cloud-controller-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  placement-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  openstack-dashboard-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  cinder-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  octavia-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  barbican-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  magnum-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}}
  ceph-radosgw-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} # B1 -- un-deferred
  designate-hacluster: {charm: hacluster, channel: 2.4/stable, options: {cluster_count: 1}, bindings: {'': metal-admin, ha: metal-internal, hanode: metal-internal, pacemaker-remote: metal-internal, peer-availability: metal-internal}} # D-106 -- reactivated 2026-07-10, supersedes D-019 deferral
  # vault-hacluster:                 { charm: hacluster, channel: 2.4/stable }   # C1: vault single-unit on mysql; HA at Roosevelt

  # memcached: nova-cloud-controller token/cell caching (BUNDLEFIX-004)
  memcached:
    charm: memcached
    channel: latest/stable
    num_units: 1
    to: [lxd:8]
    bindings:
      '': metal-admin
      cache: metal-internal
      cluster: metal-internal
    constraints: arch=amd64

relations:
  - [nova-cloud-controller:memcache, memcached:cache]

  # ---- Vault (single unit, MySQL storage backend via vault-mysql-router; C1 -- etcd+easyrsa removed)
  - [vault-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [vault:shared-db, vault-mysql-router:shared-db]
  - [mysql-innodb-cluster:certificates, vault:certificates]
  # - [vault:ha, vault-hacluster:ha]                   # vault de-HA'd on testcloud (C1/BUNDLEFIX-002); HA backend a Roosevelt item

  # ---- Keystone (identity, hub of all OS service relations)
  - [keystone-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [keystone-mysql-router:shared-db, keystone:shared-db]
  - [keystone:certificates, vault:certificates]
  - [keystone:ha, keystone-hacluster:ha]

  # ---- Glance (image)
  - [glance-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [glance-mysql-router:shared-db, glance:shared-db]
  - [glance:identity-service, keystone:identity-service]
  - [glance:certificates, vault:certificates]
  - [glance:ha, glance-hacluster:ha]

  # ---- Glance simplestreams sync (Octavia amphora pipeline source)
  - [glance-simplestreams-sync:identity-service, keystone:identity-service]
  - [glance-simplestreams-sync:certificates, vault:certificates]

  # ---- Nova cloud controller (NCC)
  - [ncc-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [ncc-mysql-router:shared-db, nova-cloud-controller:shared-db]
  - [nova-cloud-controller:identity-service, keystone:identity-service]
  - [nova-cloud-controller:amqp, rabbitmq-server:amqp]
  - [nova-cloud-controller:image-service, glance:image-service]
  - [nova-cloud-controller:neutron-api, neutron-api:neutron-api]
  - [nova-cloud-controller:cloud-compute, nova-compute:cloud-compute]
  - [nova-cloud-controller:cinder-volume-service, cinder:cinder-volume-service]
  - [nova-cloud-controller:certificates, vault:certificates]
  - [nova-cloud-controller:ha, nova-cloud-controller-hacluster:ha]

  # ---- Nova compute
  - [nova-compute:amqp, rabbitmq-server:amqp]
  - [nova-compute:image-service, glance:image-service]

  # ---- Placement
  - [placement-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [placement-mysql-router:shared-db, placement:shared-db]
  - [placement:identity-service, keystone:identity-service]
  - [placement:placement, nova-cloud-controller:placement]
  - [placement:certificates, vault:certificates]
  - [placement:ha, placement-hacluster:ha]

  # ---- Neutron API + OVN
  - [neutron-api-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [neutron-api-mysql-router:shared-db, neutron-api:shared-db]
  - [neutron-api:identity-service, keystone:identity-service]
  - [neutron-api:amqp, rabbitmq-server:amqp]
  - [neutron-api:certificates, vault:certificates]
  - [neutron-api-plugin-ovn:neutron-plugin, neutron-api:neutron-plugin-api-subordinate]
  - [neutron-api-plugin-ovn:ovsdb-cms, ovn-central:ovsdb-cms]
  - [neutron-api-plugin-ovn:certificates, vault:certificates]
  - [ovn-central:certificates, vault:certificates]
  - [ovn-chassis:ovsdb, ovn-central:ovsdb]
  - [ovn-chassis:nova-compute, nova-compute:neutron-plugin]
  - [ovn-chassis:certificates, vault:certificates]
  - [neutron-api:ha, neutron-api-hacluster:ha]

  # ---- Cinder + cinder-ceph
  - [cinder-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [cinder-mysql-router:shared-db, cinder:shared-db]
  - [cinder:identity-service, keystone:identity-service]
  - [cinder:amqp, rabbitmq-server:amqp]
  - [cinder:image-service, glance:image-service]
  - [cinder:certificates, vault:certificates]
  - [cinder-ceph:storage-backend, cinder:storage-backend]
  - [cinder-ceph:ceph, ceph-mon:client]
  - [cinder-ceph:ceph-access, nova-compute:ceph-access]
  - [cinder:ha, cinder-hacluster:ha]
  # ---- cinder-backup (D-108, 2026-07-10 -- subordinate, confirmed via real metadata.yaml)
  - [cinder-backup:backup-backend, cinder:backup-backend]
  - [cinder-backup:ceph, ceph-mon:client]

  # ---- Ceph mon + osd + radosgw
  - [ceph-mon:osd, ceph-osd:mon]
  - [ceph-mon:client, nova-compute:ceph]
  - [ceph-mon:client, glance:ceph]
  - [ceph-radosgw:mon, ceph-mon:radosgw]
  - [ceph-radosgw:identity-service, keystone:identity-service]
  - [ceph-radosgw:certificates, vault:certificates]
  - [ceph-radosgw:ha, ceph-radosgw-hacluster:ha]      # B1 -- un-deferred
  # ---- ceph-rbd-mirror (D-108, 2026-07-10) -- ceph-local only; ceph-remote
  # (the peer DC's ceph-mon) is a cross-model relation, NOT wired here -- see
  # the app-block comment above.
  - [ceph-rbd-mirror:ceph-local, ceph-mon:rbd-mirror]

  # ---- OpenStack Dashboard (Horizon)
  - [dashboard-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [dashboard-mysql-router:shared-db, openstack-dashboard:shared-db]
  - [openstack-dashboard:identity-service, keystone:identity-service]
  - [openstack-dashboard:certificates, vault:certificates]
  - [openstack-dashboard:ha, openstack-dashboard-hacluster:ha]

  # ---- Octavia (LBaaS)
  # CRITICAL: octavia:certificates <-> vault:certificates MUST be present at deploy time
  - [octavia-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [octavia-mysql-router:shared-db, octavia:shared-db]
  - [octavia:identity-service, keystone:identity-service]
  - [octavia:amqp, rabbitmq-server:amqp]
  - [octavia:neutron-api, neutron-api:neutron-load-balancer]
  - [octavia:certificates, vault:certificates]
  - [octavia-dashboard:dashboard, openstack-dashboard:dashboard-plugin]
  - [ovn-chassis-octavia:ovsdb, ovn-central:ovsdb]
  - [ovn-chassis-octavia:ovsdb-subordinate, octavia:ovsdb-subordinate]
  - [ovn-chassis-octavia:certificates, vault:certificates]
  # Octavia amphora image pipeline
  - [octavia-diskimage-retrofit:juju-info, glance-simplestreams-sync:juju-info]
  - [octavia-diskimage-retrofit:identity-credentials, keystone:identity-credentials]
  - [octavia:ha, octavia-hacluster:ha]

  # ---- Barbican (secrets)
  - [barbican-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [barbican-mysql-router:shared-db, barbican:shared-db]
  - [barbican:identity-service, keystone:identity-service]
  - [barbican:amqp, rabbitmq-server:amqp]
  - [barbican:certificates, vault:certificates]
  - [barbican:secrets, barbican-vault:secrets]
  - [barbican-vault:certificates, vault:certificates]
  - [barbican-vault:secrets-storage, vault:secrets]
  - [barbican:ha, barbican-hacluster:ha]

  # ---- Magnum (Layer A only; CAPI graft is Layer B/runbooks phase-06..08)
  - [magnum-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [magnum:shared-db, magnum-mysql-router:shared-db]
  - [magnum:identity-service, keystone:identity-service]
  - [magnum:amqp, rabbitmq-server:amqp]
  - [magnum:certificates, vault:certificates]
  - [magnum-dashboard:dashboard, openstack-dashboard:dashboard-plugin]
  - [magnum:ha, magnum-hacluster:ha]

  # ---- Designate (DNS) -- D-106, 2026-07-10. dns-backend/ha/coordinator-
  # memcached endpoint names CONFIRMED via charm-designate's real
  # metadata.yaml; shared-db/identity-service/amqp/certificates follow the
  # SAME standard names every other charms.openstack-family charm in this
  # bundle already uses successfully (pattern-matched, not independently
  # re-fetched for designate specifically -- flag if `juju deploy --dry-run`
  # disagrees).
  - [designate-mysql-router:db-router, mysql-innodb-cluster:db-router]
  - [designate-mysql-router:shared-db, designate:shared-db]
  - [designate:identity-service, keystone:identity-service]
  - [designate:amqp, rabbitmq-server:amqp]
  - [designate:certificates, vault:certificates]
  - [designate:coordinator-memcached, memcached:cache]
  - [designate:dns-backend, designate-bind:dns-backend]
  - [designate:ha, designate-hacluster:ha]
