# ============================================================
# 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)
# HA chain:         hacluster subordinates + dual VIPs + :ha relations ACTIVE for 11 API charms
#                   (10 prior + ceph-radosgw, un-deferred). 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
    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: storage
    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).

  # =====================================================================
  # Secrets: Barbican
  # =====================================================================

    bindings:
      '': metal-admin
      certificates: metal-internal
      identity-credentials: metal-internal
  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

  # =====================================================================
  # 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)

    bindings:
      '': metal-admin
      certificates: metal-internal
      secrets: metal-internal
      secrets-storage: metal-internal
  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

  # =====================================================================
  # HA Cluster Subordinates (11 active for v1: 10 API charms + ceph-radosgw)
  # =====================================================================
  # 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).
  # designate-hacluster stays deferred (D-019).
  #
    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
  # vault-hacluster:                 { charm: hacluster, channel: 2.4/stable }   # C1: vault single-unit on mysql; HA at Roosevelt
  # v2-deferred (D-019): designate-hacluster: { charm: hacluster, channel: 2.4/stable }

  # 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]

  # ---- 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

  # ---- 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]
