diff --git a/bundle.yaml b/bundle.yaml index e9242da..3757116 100644 --- a/bundle.yaml +++ b/bundle.yaml @@ -482,7 +482,18 @@ # v2-deferred: ceph-radosgw-hacluster: { charm: hacluster, channel: 2.4/stable } # 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: *internal-bindings + constraints: arch=amd64 + relations: + - [nova-cloud-controller:memcache, memcached:cache] # ---- Vault HA backend chain (NEW for Caracal v1; chicken-and-egg via easyrsa) - [easyrsa:client, etcd:certificates] # easyrsa issues etcd TLS one-time diff --git a/bundle.yaml.bak-20260529-005941 b/bundle.yaml.bak-20260529-005941 new file mode 100644 index 0000000..e9242da --- /dev/null +++ b/bundle.yaml.bak-20260529-005941 @@ -0,0 +1,614 @@ +# ============================================================ +# Caracal 2024.1 — VR0 DC0 Omega Cloud testcloud rebuild bundle +# ============================================================ +# Generated: 2026-05-22 +# 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 +# HA chain: ALL hacluster subordinates + vip configs + :ha relations COMMENTED OUT +# until NetBox VIP allocations land in 10.12.4.224-.254 +# Vault HA: etcd backend + easyrsa CA bootstrap live; vault-hacluster commented +# Magnum: Layer A only — CAPI driver graft is Layer B (runbooks/04a + 05) +# Octavia: lb-mgmt PKI options present but VALUES commented out — source from +# either Bobcat backup (~/backups/pre-caracal-destroy-2026-05-22/) +# or fresh octavia-cert-runbook (TBD) +# OVN tunnels: remain on metal space (Bobcat-proven); enp8s0 3_data v2 improvement +# 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): + 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 via etcd + easyrsa + 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 + +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) ---------- + api-bindings: &api-bindings + "": metal + public: provider + + # ----- Bindings for backend / internal-only charms (all metal) --------------- + # Used for ceph-mon (Ceph public network IS metal, not OpenStack public), + # ceph-osd, ovn-central, mysql-innodb-cluster, rabbitmq-server, nova-compute, etc. + internal-bindings: &internal-bindings + "": metal + +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 + +applications: + + # ===================================================================== + # Datastores: MySQL InnoDB Cluster, RabbitMQ, Vault + HA backend + # ===================================================================== + + mysql-innodb-cluster: + charm: mysql-innodb-cluster + channel: 8.0/stable + num_units: 3 + to: [lxd:8, lxd:9, lxd:10] + bindings: *internal-bindings + constraints: arch=amd64 + + rabbitmq-server: + charm: rabbitmq-server + channel: 3.9/stable + num_units: 1 + to: [lxd:10] + bindings: *internal-bindings + constraints: arch=amd64 + + vault: + charm: vault + channel: 1.8/stable + num_units: 1 # 3 on Roosevelt (D-009) + to: [lxd:11] + bindings: *internal-bindings + constraints: arch=amd64 + + vault-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + etcd: + charm: etcd + channel: latest/stable # support charm; not in OS delivery table + num_units: 3 # Vault HA backend (D-006) + to: [lxd:8, lxd:9, lxd:10] + bindings: *internal-bindings + constraints: arch=amd64 + # Note: etcd charm has its OWN `channel:` config option (controls etcd snap). + # Leaving at charm default; revisit if a specific etcd binary version is needed. + + easyrsa: + charm: easyrsa + channel: latest/stable + num_units: 1 # One-shot CA for etcd bootstrap (D-006) + to: [lxd:8] + bindings: *internal-bindings + constraints: arch=amd64 + + # ===================================================================== + # Identity: Keystone + # ===================================================================== + + keystone: + charm: keystone + channel: 2024.1/stable + num_units: 1 # 3 on Roosevelt (D-009) + to: [lxd:8] + options: + vip: 10.12.4.229 + os-public-hostname: keystone.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + keystone-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + # ===================================================================== + # Image: Glance + simplestreams-sync + # ===================================================================== + + glance: + charm: glance + channel: 2024.1/stable + num_units: 1 + to: [lxd:11] + options: + vip: 10.12.4.228 + os-public-hostname: glance.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + glance-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + glance-simplestreams-sync: + charm: glance-simplestreams-sync + channel: 2024.1/stable + num_units: 1 + to: [lxd:8] + bindings: *internal-bindings + 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.232 + os-public-hostname: nova.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + 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 + enable-resize: true + migration-auth-type: ssh + resume-guests-state-on-host-boot: true + virt-type: qemu # Testcloud nested-KVM; Roosevelt will use 'kvm' + bindings: *internal-bindings + constraints: arch=amd64 + storage: + ephemeral-device: loop,10240M + + ncc-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + placement: + charm: placement + channel: 2024.1/stable + num_units: 1 + to: [lxd:11] + options: + vip: 10.12.4.235 + os-public-hostname: placement.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + placement-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + # ===================================================================== + # 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.231 + os-public-hostname: neutron.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + neutron-api-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + neutron-api-plugin-ovn: + charm: neutron-api-plugin-ovn + channel: 2024.1/stable + + ovn-central: + charm: ovn-central + channel: 24.03/stable + num_units: 3 + to: [lxd:8, lxd:9, lxd:10] + bindings: *internal-bindings + 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 ignored. + ovn-chassis: + charm: ovn-chassis + channel: 24.03/stable + options: + ovn-bridge-mappings: physnet1:br-ex + 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 + + # ovn-chassis-octavia: separate ovn-chassis app, subordinate to octavia. + # No bridge-interface-mappings — matches Bobcat-proven pattern (Octavia mgmt + # traffic rides Neutron tenant overlay; no external physnet bridge needed here). + ovn-chassis-octavia: + charm: ovn-chassis + channel: 24.03/stable + + # ===================================================================== + # Block Storage: Cinder + cinder-ceph + # ===================================================================== + + 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.226 + os-public-hostname: cinder.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + storage: + block-devices: loop,10240M + + cinder-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + cinder-ceph: + charm: cinder-ceph + channel: 2024.1/stable + + # ===================================================================== + # Ceph: mon + osd + radosgw (Squid release per D-005) + # ===================================================================== + + 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: *internal-bindings # Ceph 'public' here = clients on metal, NOT OS public API + constraints: arch=amd64 + + 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 2026-05-22 + bindings: *internal-bindings + constraints: arch=amd64 tags=openstack + + ceph-radosgw: + charm: ceph-radosgw + channel: squid/stable + num_units: 1 + to: [lxd:8] + options: + source: *ceph-source + # v2-deferred: ceph-radosgw HA deferred to v2 per workstream-2 decision. + # vip slot 10.12.4.225 reserved for ceph-radosgw VIP in v2. + # See also commented ceph-radosgw-hacluster app + :ha relation below. + bindings: *api-bindings # radosgw IS externally-facing (S3/Swift API) + 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.234 + os-public-hostname: horizon.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + dashboard-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + # ===================================================================== + # Load Balancer: Octavia + # ===================================================================== + # CRITICAL: vault:certificates must be in bundle from day-one (post-deploy add + # causes 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 + # ----- 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.233 + os-public-hostname: octavia.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + octavia-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + octavia-dashboard: + charm: octavia-dashboard + channel: 2024.1/stable + + octavia-diskimage-retrofit: + charm: octavia-diskimage-retrofit + channel: 2024.1/stable + options: + amp-image-tag: octavia-amphora + + # ===================================================================== + # Secrets: Barbican + # ===================================================================== + + barbican: + charm: barbican + channel: 2024.1/stable + num_units: 1 + to: [lxd:11] + options: + openstack-origin: *openstack-origin + vip: 10.12.4.224 + os-public-hostname: barbican.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + barbican-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + 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: + # 1. capi-mgmt VM with k3s + CAPI operators (runbook 04a) + # 2. pip install magnum-capi-helm==1.1.0 into magnum venv (runbook 05) + # 3. /etc/magnum/magnum.conf.d/99-capi.conf with enabled_drivers + # 4. Install kubeconfig at /etc/magnum/kubeconfig + # 5. Create Keystone capi-mgmt project + capo user + app credential + + magnum: + charm: magnum + channel: 2024.1/stable + num_units: 1 + to: [lxd:9] + options: + openstack-origin: *openstack-origin + region: RegionOne + vip: 10.12.4.230 + os-public-hostname: magnum.omega.dc0.vr0.cloud.neumatrix.local + bindings: *api-bindings + constraints: arch=amd64 + + magnum-mysql-router: + charm: mysql-router + channel: 8.0/stable + bindings: *internal-bindings + + magnum-dashboard: + charm: magnum-dashboard + channel: 2024.1/stable + + # ===================================================================== + # HA Cluster Subordinates (11 active for v1; ceph-radosgw + designate deferred to v2) + # ===================================================================== + # Channel: 2.4/stable (per Caracal Charm Delivery table, D-002 verified 2026-05-22). + # VIPs allocated from provider /22 range 10.12.4.224-.254 per D-003. + # NetBox IPAddress records queued post-deployment (engineer review pending). + # See workstream-2 decision (2026-05-22). + # + keystone-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + glance-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + neutron-api-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + nova-cloud-controller-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + placement-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + openstack-dashboard-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + cinder-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + octavia-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + barbican-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + magnum-hacluster: { charm: hacluster, channel: 2.4/stable, options: { cluster_count: 1 } } + # vault-hacluster: { charm: hacluster, channel: 2.4/stable } + # v2-deferred: ceph-radosgw-hacluster: { charm: hacluster, channel: 2.4/stable } + # v2-deferred (D-019): designate-hacluster: { charm: hacluster, channel: 2.4/stable } + +relations: + + # ---- Vault HA backend chain (NEW for Caracal v1; chicken-and-egg via easyrsa) + - [easyrsa:client, etcd:certificates] # easyrsa issues etcd TLS one-time + - [vault:etcd, etcd:db] # vault uses etcd as HA backend + - [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] + + # ---- 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] + # v2-deferred: - [ceph-radosgw:ha, ceph-radosgw-hacluster:ha] + + # ---- 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/runbook 05) + - [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] diff --git a/fix-bundle-add-memcached.py b/fix-bundle-add-memcached.py new file mode 100644 index 0000000..da7bebb --- /dev/null +++ b/fix-bundle-add-memcached.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +""" +fix-bundle-add-memcached.py (BUNDLEFIX-004, part 2) + +Adds the `memcached` application AND the +`nova-cloud-controller:memcache <-> memcached:cache` relation to the Caracal +bundle, matching the live `juju deploy memcached` + `juju integrate` already +applied to the running model. + +Why: nova-cloud-controller treats `memcache` as a required relation. The Caracal +rebuild omitted memcached entirely, so a fresh `juju deploy` of the bundle would +leave nova-cc blocked on "Missing relations: memcache" (no instance scheduling). + +App block added (placement to: [lxd:8] = openstack0, where it landed live; metal +space; latest/stable, the only stable channel for the memcached charm): + + memcached: + charm: memcached + channel: latest/stable + num_units: 1 + to: [lxd:8] + bindings: *internal-bindings + constraints: arch=amd64 + +Relation added: + - [nova-cloud-controller:memcache, memcached:cache] + +Safe by construction: line edits (preserve anchors/comments/formatting), +timestamped .bak, unified diff, idempotent, yaml.safe_load verification. + +Usage: python3 fix-bundle-add-memcached.py [path/to/bundle.yaml] (default ./bundle.yaml) +""" +import sys, os, difflib, datetime + +DEFAULT = "bundle.yaml" + +APP_BLOCK = [ + "", + " # memcached: nova-cloud-controller token/cell caching (BUNDLEFIX-004)", + " memcached:", + " charm: memcached", + " channel: latest/stable", + " num_units: 1", + " to: [lxd:8]", + " bindings: *internal-bindings", + " constraints: arch=amd64", + "", +] +RELATION_LINE = " - [nova-cloud-controller:memcache, memcached:cache]" + + +def main(): + path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT + if not os.path.isfile(path): + print(f"[ABORT] not found: {path}") + return 2 + + original = open(path, encoding="utf-8").read() + lines = original.splitlines() + + have_app = any(l.strip().startswith("memcached:") for l in lines) + have_rel = "memcached:cache" in original + if have_app and have_rel: + print("[OK/IDEMPOTENT] memcached app and relation already present; no change.") + return 0 + if have_app != have_rel: + print(f"[ABORT] partial state (app={have_app}, relation={have_rel}); fix by hand to avoid duplication.") + return 3 + + # Bundle order here is description -> variables -> machines -> applications -> relations, + # so `relations:` is the END of the applications section. Anchor BOTH inserts to it: + # the app block goes immediately before `relations:` (last app), the relation immediately after. + rel_idx = next((i for i, l in enumerate(lines) if l.rstrip() == "relations:"), None) + if rel_idx is None: + print("[ABORT] could not find top-level 'relations:' key.") + return 4 + + out = [] + for i, l in enumerate(lines): + if i == rel_idx: + out.extend(APP_BLOCK) # app block: end of applications (just before relations:) + out.append(l) + if i == rel_idx: + out.append(RELATION_LINE) # relation: first entry after relations: + new = "\n".join(out) + ("\n" if original.endswith("\n") else "") + + print("=== unified diff ===") + print("\n".join(difflib.unified_diff( + original.splitlines(), new.splitlines(), + fromfile=f"{path} (orig)", tofile=f"{path} (new)", lineterm=""))) + + try: + import yaml + d = yaml.safe_load(new) + a = d["applications"] + rels = d.get("relations", []) + assert "memcached" in a, "memcached app missing after edit" + assert a["memcached"].get("charm") == "memcached", "charm != memcached" + assert a["memcached"].get("bindings") == {"": "metal"}, f"bindings={a['memcached'].get('bindings')}" + mc = [r for r in rels if any("memcache" in str(x) for x in r)] + assert mc, "memcache relation missing after edit" + print(f"[VERIFY] OK: memcached app present, bindings {{'': 'metal'}}, relation {mc}") + print(f"[VERIFY] totals now: apps={len(a)} relations={len(rels)}") + except ImportError: + print("[WARN] PyYAML missing; skipped semantic verify (re-verify on jumphost after pull).") + except Exception as e: + print(f"[ABORT] verification failed: {e}") + return 5 + + ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + bak = f"{path}.bak-{ts}" + open(bak, "w", encoding="utf-8").write(original) + open(path, "w", encoding="utf-8").write(new) + print(f"[WROTE] {path} (backup: {bak})") + return 0 + + +if __name__ == "__main__": + sys.exit(main())