From df22db0fe4692af89238f4e516a77a1672680c62 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Mon, 13 Oct 2025 16:23:11 -0500 Subject: [PATCH 01/11] feat: upgrade to OpenStack 2025.2 This upgrades every place we use OpenStack 2025.2 (or its components) to OpenStack 2025.2 versions. Removes any settings overrides that were necessary for 2025.1 as well. Dropped patches which are included as well. --- .github/workflows/containers.yaml | 2 +- components/images-openstack.yaml | 133 ++++--- .../ironic/ironic-ks-user-baremetal.yaml | 2 +- components/ironic/values.yaml | 2 +- components/keystone/values.yaml | 4 + components/neutron/configmap-neutron-bin.yaml | 16 +- components/nova/values.yaml | 2 +- components/octavia/values.yaml | 5 - ...ensor-keystone-automation-user-delete.yaml | 2 +- ...ensor-keystone-automation-user-upsert.yaml | 2 +- .../0002_skip_reboot_firmware_update.patch | 331 ------------------ containers/ironic/patches/series | 2 - containers/openstack-client/Dockerfile | 2 +- docs/operator-guide/openstack-ironic.md | 2 +- go/understackctl/cmd/helmConfig/helmConfig.go | 2 +- python/cinder-understack/pyproject.toml | 4 +- python/cinder-understack/uv.lock | 17 +- python/ironic-understack/pyproject.toml | 2 +- python/ironic-understack/uv.lock | 112 ++++-- python/neutron-understack/pyproject.toml | 2 +- python/neutron-understack/uv.lock | 49 ++- .../alert-automation-neutron-agent-down.yaml | 2 +- .../workflowtemplates/enroll-server.yaml | 6 +- .../workflowtemplates/reclean-server.yaml | 4 +- 24 files changed, 223 insertions(+), 484 deletions(-) delete mode 100644 containers/ironic/patches/0002_skip_reboot_firmware_update.patch diff --git a/.github/workflows/containers.yaml b/.github/workflows/containers.yaml index 1238b2895..5f0091954 100644 --- a/.github/workflows/containers.yaml +++ b/.github/workflows/containers.yaml @@ -23,7 +23,7 @@ on: types: [checks_requested] env: - OPENSTACK_VERSION: 2025.1 + OPENSTACK_VERSION: 2025.2 jobs: openstack: diff --git a/components/images-openstack.yaml b/components/images-openstack.yaml index 2bcf45592..441414df5 100644 --- a/components/images-openstack.yaml +++ b/components/images-openstack.yaml @@ -5,67 +5,67 @@ images: tags: # these are common across all these OpenStack Helm installations - bootstrap: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - db_init: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - db_drop: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - ks_user: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - ks_service: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - ks_endpoints: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" + bootstrap: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + db_init: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + db_drop: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + ks_user: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + ks_service: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + ks_endpoints: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" # keystone - keystone_api: "ghcr.io/rackerlabs/understack/keystone:2025.1-ubuntu_jammy" - keystone_credential_rotate: "ghcr.io/rackerlabs/understack/keystone:2025.1-ubuntu_jammy" - keystone_credential_setup: "ghcr.io/rackerlabs/understack/keystone:2025.1-ubuntu_jammy" - keystone_db_sync: "ghcr.io/rackerlabs/understack/keystone:2025.1-ubuntu_jammy" - keystone_domain_manage: "ghcr.io/rackerlabs/understack/keystone:2025.1-ubuntu_jammy" - keystone_fernet_rotate: "ghcr.io/rackerlabs/understack/keystone:2025.1-ubuntu_jammy" - keystone_fernet_setup: "ghcr.io/rackerlabs/understack/keystone:2025.1-ubuntu_jammy" + keystone_api: "ghcr.io/rackerlabs/understack/keystone:2025.2-ubuntu_jammy" + keystone_credential_rotate: "ghcr.io/rackerlabs/understack/keystone:2025.2-ubuntu_jammy" + keystone_credential_setup: "ghcr.io/rackerlabs/understack/keystone:2025.2-ubuntu_jammy" + keystone_db_sync: "ghcr.io/rackerlabs/understack/keystone:2025.2-ubuntu_jammy" + keystone_domain_manage: "ghcr.io/rackerlabs/understack/keystone:2025.2-ubuntu_jammy" + keystone_fernet_rotate: "ghcr.io/rackerlabs/understack/keystone:2025.2-ubuntu_jammy" + keystone_fernet_setup: "ghcr.io/rackerlabs/understack/keystone:2025.2-ubuntu_jammy" # ironic - ironic_api: "ghcr.io/rackerlabs/understack/ironic:2025.1-ubuntu_jammy" - ironic_conductor: "ghcr.io/rackerlabs/understack/ironic:2025.1-ubuntu_jammy" - ironic_pxe: "ghcr.io/rackerlabs/understack/ironic:2025.1-ubuntu_jammy" - ironic_pxe_init: "ghcr.io/rackerlabs/understack/ironic:2025.1-ubuntu_jammy" + ironic_api: "ghcr.io/rackerlabs/understack/ironic:2025.2-ubuntu_jammy" + ironic_conductor: "ghcr.io/rackerlabs/understack/ironic:2025.2-ubuntu_jammy" + ironic_pxe: "ghcr.io/rackerlabs/understack/ironic:2025.2-ubuntu_jammy" + ironic_pxe_init: "ghcr.io/rackerlabs/understack/ironic:2025.2-ubuntu_jammy" ironic_pxe_http: "docker.io/nginx:1.13.3" - ironic_db_sync: "ghcr.io/rackerlabs/understack/ironic:2025.1-ubuntu_jammy" + ironic_db_sync: "ghcr.io/rackerlabs/understack/ironic:2025.2-ubuntu_jammy" # these want curl which apparently is in the heat image - ironic_manage_cleaning_network: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - ironic_retrive_cleaning_network: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - ironic_retrive_swift_config: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" + ironic_manage_cleaning_network: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + ironic_retrive_cleaning_network: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + ironic_retrive_swift_config: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" # neutron - neutron_db_sync: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_dhcp: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_l3: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_l2gw: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_linuxbridge_agent: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_metadata: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_ovn_metadata: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_openvswitch_agent: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_server: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_rpc_server: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_bagpipe_bgp: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" - neutron_netns_cleanup_cron: "ghcr.io/rackerlabs/understack/neutron:2025.1-ubuntu_jammy" + neutron_db_sync: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_dhcp: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_l3: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_l2gw: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_linuxbridge_agent: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_metadata: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_ovn_metadata: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_openvswitch_agent: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_server: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_rpc_server: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_bagpipe_bgp: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" + neutron_netns_cleanup_cron: "ghcr.io/rackerlabs/understack/neutron:2025.2-ubuntu_jammy" # nova - nova_api: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_cell_setup: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_cell_setup_init: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" - nova_compute: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_compute_ironic: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_compute_ssh: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_conductor: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_db_sync: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_novncproxy: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_novncproxy_assets: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_scheduler: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_spiceproxy: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" - nova_spiceproxy_assets: "ghcr.io/rackerlabs/understack/nova:2025.1-ubuntu_jammy" + nova_api: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_cell_setup: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_cell_setup_init: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" + nova_compute: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_compute_ironic: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_compute_ssh: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_conductor: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_db_sync: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_novncproxy: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_novncproxy_assets: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_scheduler: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_spiceproxy: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" + nova_spiceproxy_assets: "ghcr.io/rackerlabs/understack/nova:2025.2-ubuntu_jammy" nova_service_cleaner: "docker.io/openstackhelm/ceph-config-helper:latest-ubuntu_jammy" # placement - placement: "quay.io/airshipit/placement:2025.1-ubuntu_jammy" - placement_db_sync: "quay.io/airshipit/placement:2025.1-ubuntu_jammy" + placement: "quay.io/airshipit/placement:2025.2-ubuntu_jammy" + placement_db_sync: "quay.io/airshipit/placement:2025.2-ubuntu_jammy" # openvswitch openvswitch_db_server: "docker.io/openstackhelm/openvswitch:ubuntu_jammy-dpdk-20250127" @@ -78,13 +78,13 @@ images: ovn_controller: "docker.io/openstackhelm/ovn:ubuntu_jammy-20250111" # horizon - horizon: "quay.io/airshipit/horizon:2025.1-ubuntu_jammy" - horizon_db_sync: "quay.io/airshipit/horizon:2025.1-ubuntu_jammy" + horizon: "quay.io/airshipit/horizon:2025.2-ubuntu_jammy" + horizon_db_sync: "quay.io/airshipit/horizon:2025.2-ubuntu_jammy" # glance - glance_api: "quay.io/airshipit/glance:2025.1-ubuntu_jammy" - glance_db_sync: "quay.io/airshipit/glance:2025.1-ubuntu_jammy" - glance_metadefs_load: "quay.io/airshipit/glance:2025.1-ubuntu_jammy" + glance_api: "quay.io/airshipit/glance:2025.2-ubuntu_jammy" + glance_db_sync: "quay.io/airshipit/glance:2025.2-ubuntu_jammy" + glance_metadefs_load: "quay.io/airshipit/glance:2025.2-ubuntu_jammy" glance_storage_init: "docker.io/openstackhelm/ceph-config-helper:latest-ubuntu_jammy" # skyline @@ -92,22 +92,21 @@ images: skyline_db_sync: "quay.io/airshipit/skyline:latest" # cinder - cinder_api: "ghcr.io/rackerlabs/understack/cinder:2025.1-ubuntu_jammy" - cinder_db_sync: "ghcr.io/rackerlabs/understack/cinder:2025.1-ubuntu_jammy" - cinder_scheduler: "ghcr.io/rackerlabs/understack/cinder:2025.1-ubuntu_jammy" - cinder_volume: "ghcr.io/rackerlabs/understack/cinder:2025.1-ubuntu_jammy" - cinder_volume_usage_audit: "ghcr.io/rackerlabs/understack/cinder:2025.1-ubuntu_jammy" - cinder_db_purge: "ghcr.io/rackerlabs/understack/cinder:2025.1-ubuntu_jammy" - cinder_backup: "ghcr.io/rackerlabs/understack/cinder:2025.1-ubuntu_jammy" + cinder_api: "ghcr.io/rackerlabs/understack/cinder:2025.2-ubuntu_jammy" + cinder_db_sync: "ghcr.io/rackerlabs/understack/cinder:2025.2-ubuntu_jammy" + cinder_scheduler: "ghcr.io/rackerlabs/understack/cinder:2025.2-ubuntu_jammy" + cinder_volume: "ghcr.io/rackerlabs/understack/cinder:2025.2-ubuntu_jammy" + cinder_volume_usage_audit: "ghcr.io/rackerlabs/understack/cinder:2025.2-ubuntu_jammy" + cinder_db_purge: "ghcr.io/rackerlabs/understack/cinder:2025.2-ubuntu_jammy" + cinder_backup: "ghcr.io/rackerlabs/understack/cinder:2025.2-ubuntu_jammy" cinder_storage_init: "docker.io/openstackhelm/ceph-config-helper:latest-ubuntu_jammy" cinder_backup_storage_init: "docker.io/openstackhelm/ceph-config-helper:latest-ubuntu_jammy" # octavia - octavia_api: "ghcr.io/rackerlabs/understack/octavia:2025.1-ubuntu_jammy" - octavia_db_sync: "ghcr.io/rackerlabs/understack/octavia:2025.1-ubuntu_jammy" - octavia_driver_agent: "ghcr.io/rackerlabs/understack/octavia:2025.1-ubuntu_jammy" - octavia_worker: "ghcr.io/rackerlabs/understack/octavia:2025.1-ubuntu_jammy" - octavia_housekeeping: "ghcr.io/rackerlabs/understack/octavia:2025.1-ubuntu_jammy" - octavia_health_manager: "ghcr.io/rackerlabs/understack/octavia:2025.1-ubuntu_jammy" - octavia_health_manager_init: "quay.io/airshipit/heat:2025.1-ubuntu_jammy" + octavia_api: "ghcr.io/rackerlabs/understack/octavia:2025.2-ubuntu_jammy" + octavia_db_sync: "ghcr.io/rackerlabs/understack/octavia:2025.2-ubuntu_jammy" + octavia_worker: "ghcr.io/rackerlabs/understack/octavia:2025.2-ubuntu_jammy" + octavia_housekeeping: "ghcr.io/rackerlabs/understack/octavia:2025.2-ubuntu_jammy" + octavia_health_manager: "ghcr.io/rackerlabs/understack/octavia:2025.2-ubuntu_jammy" + octavia_health_manager_init: "quay.io/airshipit/heat:2025.2-ubuntu_jammy" ... diff --git a/components/ironic/ironic-ks-user-baremetal.yaml b/components/ironic/ironic-ks-user-baremetal.yaml index 21fb2c42f..e18d0bbac 100644 --- a/components/ironic/ironic-ks-user-baremetal.yaml +++ b/components/ironic/ironic-ks-user-baremetal.yaml @@ -38,7 +38,7 @@ spec: restartPolicy: OnFailure containers: - name: ks-user-baremetal - image: ghcr.io/rackerlabs/understack/openstack-client:2025.1-ubuntu_jammy + image: ghcr.io/rackerlabs/understack/openstack-client:2025.2-ubuntu_jammy imagePullPolicy: Always command: - /bin/bash diff --git a/components/ironic/values.yaml b/components/ironic/values.yaml index c8d6aa77e..a0935fc8b 100644 --- a/components/ironic/values.yaml +++ b/components/ironic/values.yaml @@ -22,7 +22,7 @@ conductor: # it is only necessary because the above pxe is disabled, its init # creates this path - name: create-tftpboot - image: quay.io/airshipit/heat:2025.1-ubuntu_jammy + image: quay.io/airshipit/heat:2025.2-ubuntu_jammy imagePullPolicy: IfNotPresent command: [bash] args: diff --git a/components/keystone/values.yaml b/components/keystone/values.yaml index 04bcbef72..3ddc338a3 100644 --- a/components/keystone/values.yaml +++ b/components/keystone/values.yaml @@ -143,6 +143,10 @@ pod: - name: oidc-secret mountPath: /etc/oidc-secret readOnly: true + - name: keystone-bin + mountPath: /var/www/cgi-bin/keystone/keystone-wsgi-public + readOnly: true + subPath: wsgi.py volumes: - name: keystone-sso secret: diff --git a/components/neutron/configmap-neutron-bin.yaml b/components/neutron/configmap-neutron-bin.yaml index 46500896b..adb5a068c 100644 --- a/components/neutron/configmap-neutron-bin.yaml +++ b/components/neutron/configmap-neutron-bin.yaml @@ -1655,18 +1655,12 @@ data: COMMAND="${@:-start}" function start () { - # (ricolin): Currently ovn have issue with uWSGI, - # let's keep using non-uWSGI way until this bug fixed: - # https://bugs.launchpad.net/neutron/+bug/1912359 - start_ovn - } + confs="--config-file /etc/neutron/neutron.conf" + confs+=" --config-file /tmp/pod-shared/ovn.ini" + confs+=" --config-file /etc/neutron/plugins/ml2/ml2_conf.ini" + confs+=" --config-dir /etc/neutron/neutron.conf.d" - function start_ovn () { - exec neutron-server \ - --config-file /etc/neutron/neutron.conf \ - --config-file /tmp/pod-shared/ovn.ini \ - --config-file /etc/neutron/plugins/ml2/ml2_conf.ini \ - --config-dir /etc/neutron/neutron.conf.d + exec uwsgi --ini /etc/neutron/neutron-api-uwsgi.ini --pyargv " $confs " } function stop () { diff --git a/components/nova/values.yaml b/components/nova/values.yaml index 47609f6e2..d3db0c638 100644 --- a/components/nova/values.yaml +++ b/components/nova/values.yaml @@ -81,7 +81,7 @@ conf: api_max_retries: 90 # number of times to retry. default is 60. api_retry_interval: 10 # number of sesconds between retries. default is 2. - # https://docs.openstack.org/nova/2025.1/admin/scheduling.html#the-filter-scheduler + # https://docs.openstack.org/nova/2025.2/admin/scheduling.html#the-filter-scheduler filter_scheduler: available_filters: nova.scheduler.filters.all_filters enabled_filters: diff --git a/components/octavia/values.yaml b/components/octavia/values.yaml index 18b4bf3e2..ecc88c691 100644 --- a/components/octavia/values.yaml +++ b/components/octavia/values.yaml @@ -52,11 +52,6 @@ conf: ovn_sb_connection: tcp:ovn-ovsdb-sb.openstack.svc.cluster.local:6642 task_flow: jobboard_enabled: false - octavia_api_uwsgi: - uwsgi: - # When upgrading to 2025.2 remove this as the wsgi script was removed - # https://opendev.org/openstack/openstack-helm/commit/b1d85c20e36adac9eaee584ef095d9c010cc1dc4 - wsgi-file: /var/lib/openstack/bin/octavia-wsgi dependencies: dynamic: diff --git a/components/site-workflows/sensors/sensor-keystone-automation-user-delete.yaml b/components/site-workflows/sensors/sensor-keystone-automation-user-delete.yaml index e50dba5f3..2468f87d8 100644 --- a/components/site-workflows/sensors/sensor-keystone-automation-user-delete.yaml +++ b/components/site-workflows/sensors/sensor-keystone-automation-user-delete.yaml @@ -31,7 +31,7 @@ spec: spec: containers: - name: openstackcli - image: quay.io/airshipit/heat:2025.1-ubuntu_jammy + image: quay.io/airshipit/heat:2025.2-ubuntu_jammy command: ["sh", "-c"] args: - | diff --git a/components/site-workflows/sensors/sensor-keystone-automation-user-upsert.yaml b/components/site-workflows/sensors/sensor-keystone-automation-user-upsert.yaml index 71c5d7a18..a1230dc9a 100644 --- a/components/site-workflows/sensors/sensor-keystone-automation-user-upsert.yaml +++ b/components/site-workflows/sensors/sensor-keystone-automation-user-upsert.yaml @@ -31,7 +31,7 @@ spec: spec: containers: - name: openstackcli - image: quay.io/airshipit/heat:2025.1-ubuntu_jammy + image: quay.io/airshipit/heat:2025.2-ubuntu_jammy command: ["sh", "-c"] args: - | diff --git a/containers/ironic/patches/0002_skip_reboot_firmware_update.patch b/containers/ironic/patches/0002_skip_reboot_firmware_update.patch deleted file mode 100644 index 1c9bcbe30..000000000 --- a/containers/ironic/patches/0002_skip_reboot_firmware_update.patch +++ /dev/null @@ -1,331 +0,0 @@ -From 1b2c01185c7907a1d17c4d90cf3bcd73fe345658 Mon Sep 17 00:00:00 2001 -From: Jacob Anders -Date: Tue, 08 Jul 2025 16:25:40 +1000 -Subject: [PATCH] Skip initial reboot to IPA when updating firmware out-of-band - -This change enables Ironic to skip initial reboot to IPA when -performing out-of-band firmware. - -Change-Id: Id055a4ddbde3dbe336717e5f06ca6eb024b90c9f -Signed-off-by: Jacob Anders ---- - -diff --git a/ironic/conductor/servicing.py b/ironic/conductor/servicing.py -index 8b385d7..6aaa4a5 100644 ---- a/ironic/conductor/servicing.py -+++ b/ironic/conductor/servicing.py -@@ -38,6 +38,7 @@ - :param disable_ramdisk: Whether to skip booting ramdisk for servicing. - """ - node = task.node -+ ramdisk_needed = None - try: - # NOTE(ghe): Valid power and network values are needed to perform - # a service operation. -@@ -54,42 +55,53 @@ - node.set_driver_internal_info('service_disable_ramdisk', - disable_ramdisk) - task.node.save() -- - utils.node_update_cache(task) - -- # Allow the deploy driver to set up the ramdisk again (necessary for IPA) -- try: -- if not disable_ramdisk: -- prepare_result = task.driver.deploy.prepare_service(task) -- else: -- LOG.info('Skipping preparing for service in-band service since ' -- 'out-of-band only service has been requested for node ' -- '%s', node.uuid) -- prepare_result = None -- except Exception as e: -- msg = (_('Failed to prepare node %(node)s for service: %(e)s') -- % {'node': node.uuid, 'e': e}) -- return utils.servicing_error_handler(task, msg, traceback=True) -- -- if prepare_result == states.SERVICEWAIT: -- # Prepare is asynchronous, the deploy driver will need to -- # set node.driver_internal_info['service_steps'] and -- # node.service_step and then make an RPC call to -- # continue_node_service to start service operations. -- task.process_event('wait') -- return - try: - conductor_steps.set_node_service_steps( - task, disable_ramdisk=disable_ramdisk) -+ except exception.InvalidParameterValue: -+ if disable_ramdisk: -+ # NOTE(janders) raising log severity since this will result -+ # in servicing failure -+ LOG.error('Failed to compose list of service steps for node ' -+ '%s', node.uuid) -+ raise -+ LOG.debug('Unable to compose list of service steps for node %(node)s ' -+ 'will retry once ramdisk is booted.', {'node': node.uuid}) -+ ramdisk_needed = True - except Exception as e: -- # Catch all exceptions and follow the error handling -- # path so things are cleaned up properly. - msg = (_('Cannot service node %(node)s: %(msg)s') - % {'node': node.uuid, 'msg': e}) - return utils.servicing_error_handler(task, msg) - - steps = node.driver_internal_info.get('service_steps', []) - step_index = 0 if steps else None -+ for step in steps: -+ step_requires_ramdisk = step.get('requires_ramdisk') -+ if step_requires_ramdisk: -+ LOG.debug('Found service step %s requiring ramdisk ' -+ 'for node %s', step, task.node.uuid) -+ ramdisk_needed = True -+ -+ if ramdisk_needed and not disable_ramdisk: -+ try: -+ prepare_result = task.driver.deploy.prepare_service(task) -+ except Exception as e: -+ msg = (_('Failed to prepare node %(node)s for service: %(e)s') -+ % {'node': node.uuid, 'e': e}) -+ return utils.servicing_error_handler(task, msg, traceback=True) -+ if prepare_result == states.SERVICEWAIT: -+ # Prepare is asynchronous, the deploy driver will need to -+ # set node.driver_internal_info['service_steps'] and -+ # node.service_step and then make an RPC call to -+ # continue_node_service to start service operations. -+ task.process_event('wait') -+ return -+ else: -+ LOG.debug('Will proceed with servicing node %(node)s ' -+ 'without booting the ramdisk.', {'node': node.uuid}) -+ - do_next_service_step(task, step_index, disable_ramdisk=disable_ramdisk) - - -diff --git a/ironic/conductor/steps.py b/ironic/conductor/steps.py -index 39f75fbe..9112e6d 100644 ---- a/ironic/conductor/steps.py -+++ b/ironic/conductor/steps.py -@@ -496,9 +496,11 @@ - are accepted. - :raises: InvalidParameterValue if there is a problem with the user's - clean steps. -- :raises: NodeCleaningFailure if there was a problem getting the -+ :raises: NodeServicingFailure if there was a problem getting the - service steps. - """ -+ # FIXME(janders) it seems NodeServicingFailure is never actually raised, -+ # perhaps we should remove it? - node = task.node - steps = _validate_user_service_steps( - task, node.driver_internal_info.get('service_steps', []), -diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py -index 8afb909..f501b33 100644 ---- a/ironic/drivers/modules/deploy_utils.py -+++ b/ironic/drivers/modules/deploy_utils.py -@@ -1750,7 +1750,7 @@ - task.driver.boot.prepare_ramdisk(task, deploy_opts) - - --def reboot_to_finish_step(task, timeout=None): -+def reboot_to_finish_step(task, timeout=None, disable_ramdisk=None): - """Reboot the node into IPA to finish a deploy/clean step. - - :param task: a TaskManager instance. -@@ -1759,8 +1759,14 @@ - :returns: states.CLEANWAIT if cleaning operation in progress - or states.DEPLOYWAIT if deploy operation in progress. - """ -- disable_ramdisk = task.node.driver_internal_info.get( -- 'cleaning_disable_ramdisk') -+ if disable_ramdisk is None: -+ if task.node.provision_state in [states.CLEANING, states.CLEANWAIT]: -+ disable_ramdisk = task.node.driver_internal_info.get( -+ 'cleaning_disable_ramdisk') -+ elif task.node.provision_state in [states.SERVICING, -+ states.SERVICEWAIT]: -+ disable_ramdisk = task.node.driver_internal_info.get( -+ 'service_disable_ramdisk') - if not disable_ramdisk: - if (manager_utils.is_fast_track(task) - and not task.node.disable_power_off): -diff --git a/ironic/drivers/modules/redfish/firmware.py b/ironic/drivers/modules/redfish/firmware.py -index 2500cd5..555a921 100644 ---- a/ironic/drivers/modules/redfish/firmware.py -+++ b/ironic/drivers/modules/redfish/firmware.py -@@ -146,7 +146,7 @@ - requires_ramdisk=True) - @base.service_step(priority=0, abortable=False, - argsinfo=_FW_SETTINGS_ARGSINFO, -- requires_ramdisk=True) -+ requires_ramdisk=False) - @base.cache_firmware_components - def update(self, task, settings): - """Update the Firmware on the node using the settings for components. -@@ -181,7 +181,8 @@ - polling=True - ) - -- return deploy_utils.reboot_to_finish_step(task, timeout=wait_interval) -+ return deploy_utils.reboot_to_finish_step(task, timeout=wait_interval, -+ disable_ramdisk=True) - - def _execute_firmware_update(self, node, update_service, settings): - """Executes the next firmware update to the node -diff --git a/ironic/tests/unit/conductor/test_servicing.py b/ironic/tests/unit/conductor/test_servicing.py -index 22c2612..b46394d 100644 ---- a/ironic/tests/unit/conductor/test_servicing.py -+++ b/ironic/tests/unit/conductor/test_servicing.py -@@ -109,14 +109,21 @@ - mock_validate.side_effect = exception.NetworkError() - self.__do_node_service_validate_fail(mock_validate) - -+ @mock.patch.object(conductor_steps, 'set_node_service_steps', -+ autospec=True) - @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.validate', - autospec=True) - @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare_service', - autospec=True) - def test__do_node_service_prepare_service_fail(self, mock_prep, - mock_validate, -- service_steps=None): -- # Exception from task.driver.deploy.prepare_cleaning should cause node -+ mock_steps, -+ service_steps=[]): -+ # NOTE(janders) after removing unconditional initial reboot into -+ # ramdisk, set_node_service_steps needs to InvalidParameterValue -+ # to force boot into ramdisk -+ mock_steps.side_effect = exception.InvalidParameterValue('error') -+ # Exception from task.driver.deploy.prepare_service should cause node - # to go to SERVICEFAIL - mock_prep.side_effect = exception.InvalidParameterValue('error') - tgt_prov_state = states.ACTIVE -@@ -135,17 +142,76 @@ - self.assertFalse(node.maintenance) - self.assertIsNone(node.fault) - -+ @mock.patch.object(conductor_steps, 'set_node_service_steps', -+ autospec=True) - @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.validate', - autospec=True) - @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare_service', - autospec=True) - def test__do_node_service_prepare_service_wait(self, mock_prep, -- mock_validate): -+ mock_validate, -+ mock_steps): - service_steps = [ - {'step': 'trigger_servicewait', 'priority': 10, - 'interface': 'vendor'} - ] -+ mock_steps.side_effect = exception.InvalidParameterValue('error') -+ mock_prep.return_value = states.SERVICEWAIT -+ tgt_prov_state = states.ACTIVE -+ node = obj_utils.create_test_node( -+ self.context, driver='fake-hardware', -+ provision_state=states.SERVICING, -+ target_provision_state=tgt_prov_state, -+ vendor_interface='fake') -+ with task_manager.acquire( -+ self.context, node.uuid, shared=False) as task: -+ servicing.do_node_service(task, service_steps=service_steps) -+ node.refresh() -+ self.assertEqual(states.SERVICEWAIT, node.provision_state) -+ self.assertEqual(tgt_prov_state, node.target_provision_state) -+ mock_prep.assert_called_once_with(mock.ANY, mock.ANY) -+ mock_validate.assert_called_once_with(mock.ANY, mock.ANY) - -+ @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.validate', -+ autospec=True) -+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare_service', -+ autospec=True) -+ def test__do_node_service_out_of_band(self, mock_prep, -+ mock_validate): -+ # NOTE(janders) this test ensures ramdisk isn't prepared if -+ # steps do not require it -+ service_steps = [ -+ {'step': 'trigger_servicewait', 'priority': 10, -+ 'interface': 'vendor'} -+ ] -+ mock_prep.return_value = states.SERVICEWAIT -+ tgt_prov_state = states.ACTIVE -+ node = obj_utils.create_test_node( -+ self.context, driver='fake-hardware', -+ provision_state=states.SERVICING, -+ target_provision_state=tgt_prov_state, -+ vendor_interface='fake') -+ with task_manager.acquire( -+ self.context, node.uuid, shared=False) as task: -+ servicing.do_node_service(task, service_steps=service_steps) -+ node.refresh() -+ self.assertEqual(states.SERVICEWAIT, node.provision_state) -+ self.assertEqual(tgt_prov_state, node.target_provision_state) -+ mock_prep.assert_not_called() -+ mock_validate.assert_called_once_with(mock.ANY, mock.ANY) -+ -+ @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.validate', -+ autospec=True) -+ @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare_service', -+ autospec=True) -+ def test__do_node_service_requires_ramdisk_fallback(self, mock_prep, -+ mock_validate): -+ # NOTE(janders): here we set 'requires_ramdisk': 'True' -+ # to force ramdisk_needed to be set and trigger prepare ramdisk -+ service_steps = [ -+ {'step': 'trigger_servicewait', 'priority': 10, -+ 'interface': 'vendor', 'requires_ramdisk': 'True'} -+ ] - mock_prep.return_value = states.SERVICEWAIT - tgt_prov_state = states.ACTIVE - node = obj_utils.create_test_node( -@@ -194,11 +260,8 @@ - @mock.patch.object(conductor_steps, 'set_node_service_steps', - autospec=True) - def __do_node_service_steps_fail(self, mock_steps, mock_validate, -- service_steps=None, invalid_exc=True): -- if invalid_exc: -- mock_steps.side_effect = exception.InvalidParameterValue('invalid') -- else: -- mock_steps.side_effect = exception.NodeCleaningFailure('failure') -+ service_steps=None): -+ mock_steps.side_effect = exception.NodeCleaningFailure('failure') - tgt_prov_state = states.ACTIVE - node = obj_utils.create_test_node( - self.context, driver='fake-hardware', -@@ -224,12 +287,8 @@ - def test_do_node_service_steps_fail_poweroff(self, mock_steps, - mock_validate, - mock_power, -- service_steps=None, -- invalid_exc=True): -- if invalid_exc: -- mock_steps.side_effect = exception.InvalidParameterValue('invalid') -- else: -- mock_steps.side_effect = exception.NodeCleaningFailure('failure') -+ service_steps=None): -+ mock_steps.side_effect = exception.NodeCleaningFailure('failure') - tgt_prov_state = states.ACTIVE - self.config(poweroff_in_cleanfail=True, group='conductor') - node = obj_utils.create_test_node( -@@ -249,9 +308,7 @@ - self.assertFalse(mock_power.called) - - def test__do_node_service_steps_fail(self): -- for invalid in (True, False): -- self.__do_node_service_steps_fail(service_steps=[self.deploy_raid], -- invalid_exc=invalid) -+ self.__do_node_service_steps_fail(service_steps=[self.deploy_raid]) - - @mock.patch.object(conductor_steps, 'set_node_service_steps', - autospec=True) -diff --git a/releasenotes/notes/servicing-remove-initial-reboot-into-ramdisk-c1840524832435c2.yaml b/releasenotes/notes/servicing-remove-initial-reboot-into-ramdisk-c1840524832435c2.yaml -new file mode 100644 -index 0000000..296779a ---- /dev/null -+++ b/releasenotes/notes/servicing-remove-initial-reboot-into-ramdisk-c1840524832435c2.yaml -@@ -0,0 +1,7 @@ -+--- -+fixes: -+ - | -+ Removes initial, unconditional reboot into ramdisk during servicing when -+ not required by specified service steps. This reduces the total number of -+ reboots needed when performing servicing, speeding up the process. -+ diff --git a/containers/ironic/patches/series b/containers/ironic/patches/series index 457be4259..bc42a0c71 100644 --- a/containers/ironic/patches/series +++ b/containers/ironic/patches/series @@ -1,4 +1,2 @@ -0002_skip_reboot_firmware_update.patch -0001-storage-controller_mode-update.patch 0001-pass-along-physical_network-to-neutron-from-the-bare.patch 0001-Solve-IPMI-call-issue-results-in-UTF-8-format-error-.patch diff --git a/containers/openstack-client/Dockerfile b/containers/openstack-client/Dockerfile index d3118e82b..6a2218d0d 100644 --- a/containers/openstack-client/Dockerfile +++ b/containers/openstack-client/Dockerfile @@ -1,4 +1,4 @@ # syntax=docker/dockerfile:1 ARG OPENSTACK_VERSION="required_argument" -FROM quay.io/airshipit/openstack-client:${OPENSTACK_VERSION} +FROM quay.io/airshipit/openstack-client:${OPENSTACK_VERSION}-ubuntu_jammy diff --git a/docs/operator-guide/openstack-ironic.md b/docs/operator-guide/openstack-ironic.md index 10be464ef..e5bef2851 100644 --- a/docs/operator-guide/openstack-ironic.md +++ b/docs/operator-guide/openstack-ironic.md @@ -119,7 +119,7 @@ openstack baremetal node clean --clean-steps raid-clean-steps.json --disable-ram ## Build nova server to specific ironic node Sometimes we need to build to a specific baremetal node. This can be accomplished by using the -[OpenStack Nova filter schedulers](https://docs.openstack.org/nova/2025.1/admin/scheduling.html#the-filter-scheduler) +[OpenStack Nova filter schedulers](https://docs.openstack.org/nova/2025.2/admin/scheduling.html#the-filter-scheduler) hint: ``` text diff --git a/go/understackctl/cmd/helmConfig/helmConfig.go b/go/understackctl/cmd/helmConfig/helmConfig.go index d33861276..cc65788f9 100644 --- a/go/understackctl/cmd/helmConfig/helmConfig.go +++ b/go/understackctl/cmd/helmConfig/helmConfig.go @@ -102,7 +102,7 @@ func ironic() error { conductor: initContainers: - name: create-tmpdir - image: quay.io/airshipit/heat:2025.1-ubuntu_jammy + image: quay.io/airshipit/heat:2025.2-ubuntu_jammy imagePullPolicy: IfNotPresent command: [bash] args: diff --git a/python/cinder-understack/pyproject.toml b/python/cinder-understack/pyproject.toml index 13f598a4d..cc061ed71 100644 --- a/python/cinder-understack/pyproject.toml +++ b/python/cinder-understack/pyproject.toml @@ -24,8 +24,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", ] dependencies = [ - "cinder>=26.0.0,<27", - "oslo.utils>=6.0.0,<8" + "cinder>=27.0.0,<28", + "oslo.utils>=9.0.0,<10" ] [project.urls] diff --git a/python/cinder-understack/uv.lock b/python/cinder-understack/uv.lock index f5cdf16e1..ae41bdcf2 100644 --- a/python/cinder-understack/uv.lock +++ b/python/cinder-understack/uv.lock @@ -215,7 +215,7 @@ wheels = [ [[package]] name = "cinder" -version = "26.1.0" +version = "27.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -277,9 +277,9 @@ dependencies = [ { name = "webob" }, { name = "zstd" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/eb/29065874c15578e38c8b3ee21a987e3f1486eb52eadeb4130f3bceca89a3/cinder-26.1.0.tar.gz", hash = "sha256:8c0f132aaf462385c8ff3cf3e7e851dfc1051f7d804bf5f86343ceed296cd94f", size = 6419364, upload-time = "2025-06-21T12:47:02.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/56/c1d10d7377d8f10da95bb67c11c911308fcc3f3d4f8d9ddbe46702a0bfb7/cinder-27.0.0.tar.gz", hash = "sha256:515bf8b6147a92cc199b83c81d69588ef7f7b271ef00d7fbc25f1c45eded547d", size = 6480684, upload-time = "2025-10-01T10:47:21.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/a9/375a5834719ba3e75f5a93da2f578e3fcd2d5d260dde39084d63178f1fdc/cinder-26.1.0-py3-none-any.whl", hash = "sha256:67d3e2f696efd1aae3b1b78b574435d82f2526097f878bea72313d3968e44b2d", size = 5277161, upload-time = "2025-06-21T12:47:01.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/34/8f0154f0c87c244a4dac4a7ad28be254ec7e6b171bbded097d7ccee243a5/cinder-27.0.0-py3-none-any.whl", hash = "sha256:9af8dc0756558581cc25fabbdd20d53281d0ee94e635f19dc64f5615ec91e47c", size = 5328643, upload-time = "2025-10-01T10:47:19.424Z" }, ] [[package]] @@ -300,8 +300,8 @@ test = [ [package.metadata] requires-dist = [ - { name = "cinder", specifier = ">=26.0.0,<27" }, - { name = "oslo-utils", specifier = ">=6.0.0,<8" }, + { name = "cinder", specifier = ">=27.0.0,<28" }, + { name = "oslo-utils", specifier = ">=9.0.0,<10" }, ] [package.metadata.requires-dev] @@ -1249,7 +1249,7 @@ wheels = [ [[package]] name = "oslo-utils" -version = "7.4.0" +version = "9.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -1257,14 +1257,15 @@ dependencies = [ { name = "netaddr" }, { name = "oslo-i18n" }, { name = "packaging" }, + { name = "pbr" }, { name = "psutil" }, { name = "pyparsing" }, { name = "pyyaml" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/f9/16063f827152f862c84c7ee93e25c65435e8ee86684c22ca5c0cdc01cb45/oslo.utils-7.4.0.tar.gz", hash = "sha256:aa5dcb5daa05ddf4b534f2cdeda56f7f21485c96f5cbaf6a8c0871d803b73ece", size = 135993, upload-time = "2024-11-05T18:49:33.21Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/ec/9f12c8ded6eb7ba0774ea4a0e03bfe6cd35fea4cbc944a826c751bb49500/oslo_utils-9.1.0.tar.gz", hash = "sha256:01c3875e7cca005b59465c429f467113b5f4b04211cbd534c9ac2f152276d3b3", size = 138207, upload-time = "2025-08-25T12:49:20.128Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/d1/72a913fe4360c44fc00b2ea24b8b472b768656437a2f52e402b70cf184d5/oslo.utils-7.4.0-py3-none-any.whl", hash = "sha256:6dd15c9fc4fb98d38e5b017f2f5ae171d35a73c5f2ae62a93d5f3bfd9384074b", size = 132117, upload-time = "2024-11-05T18:49:30.616Z" }, + { url = "https://files.pythonhosted.org/packages/98/47/1303a7050bb1dc6c5cb76a178520a215a7e7181afad637adc26482d7f257/oslo_utils-9.1.0-py3-none-any.whl", hash = "sha256:8779a2db08b84abd2cb155f8314bc6a961aedafd64ee2ff9e234ecbb80251174", size = 134210, upload-time = "2025-08-25T12:49:18.644Z" }, ] [[package]] diff --git a/python/ironic-understack/pyproject.toml b/python/ironic-understack/pyproject.toml index cb3c5f52d..9e01289e4 100644 --- a/python/ironic-understack/pyproject.toml +++ b/python/ironic-understack/pyproject.toml @@ -11,7 +11,7 @@ requires-python = "~=3.10.0" readme = "README.md" license = "MIT" dependencies = [ - "ironic>=29.0,<30", + "ironic>=32.0,<33", "pyyaml~=6.0", "understack-flavor-matcher", ] diff --git a/python/ironic-understack/uv.lock b/python/ironic-understack/uv.lock index 6d7b97c42..4a2df78ff 100644 --- a/python/ironic-understack/uv.lock +++ b/python/ironic-understack/uv.lock @@ -154,6 +154,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] +[[package]] +name = "cheroot" +version = "11.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-functools" }, + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/01/5ef06df932a974d016ab9d7f93e78740b572c4020016794fd4799cdc09c6/cheroot-11.0.0.tar.gz", hash = "sha256:dd414eda6bdb15140e864bc1d1c9625030375d14cbe0b290092867368924a52f", size = 182140, upload-time = "2025-09-21T13:55:28.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/56/8808b1ae899b7d3976b401fd4e29c3cfc97bc3d46a7973f76393218cca1f/cheroot-11.0.0-py3-none-any.whl", hash = "sha256:0e3aea62026b5e70df6e63259e345bdcff404daf4b8a11204f46d4234959fccd", size = 106397, upload-time = "2025-09-21T13:55:27.312Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -172,6 +185,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/fb/08b3f4bf05da99aba8ffea52a558758def16e8516bc75ca94ff73587e7d3/construct-2.10.70-py3-none-any.whl", hash = "sha256:c80be81ef595a1a821ec69dc16099550ed22197615f4320b57cc9ce2a672cb30", size = 63020, upload-time = "2023-11-29T08:44:46.876Z" }, ] +[[package]] +name = "cotyledon" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setproctitle", marker = "sys_platform != 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/15/4f47ad3c352568d5adf9aec06279b1fa3a12a02e02da343dfae0512f2403/cotyledon-2.1.0.tar.gz", hash = "sha256:ddf5d3639efd10789c9708a89890e55529b860986e7ca7d602a13c9eb15d8709", size = 26754, upload-time = "2025-08-30T18:56:04.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/14/baf084e49f818049e111bd221075aa54caa71be134052e880c989efd55ad/cotyledon-2.1.0-py3-none-any.whl", hash = "sha256:6b32f069300832b24a3e1a69ced003d8ed725549e38a8a98adb342864adf17cf", size = 25326, upload-time = "2025-08-30T18:56:03.247Z" }, +] + [[package]] name = "coverage" version = "7.10.7" @@ -312,11 +337,14 @@ wheels = [ [[package]] name = "futurist" -version = "3.0.0" +version = "3.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/24/864408313afba48440ee3a560e1a70b62b39e6c0dfeddea9506699e6e606/futurist-3.0.0.tar.gz", hash = "sha256:6422011792414c39228e114bec5494303aaf06dcd335e4f8dd4f907f78a41f79", size = 44836, upload-time = "2024-02-23T12:20:49.61Z" } +dependencies = [ + { name = "debtcollector" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/12/786f4aaf9d396d67b1b7b90f248ff994e916605d0751d08a0344a4a785a6/futurist-3.2.1.tar.gz", hash = "sha256:01dd4f30acdfbb2e2eb6091da565eded82d8cbaf6c48a36cc7f73c11cfa7fb3f", size = 49326, upload-time = "2025-08-29T15:06:57.733Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/2b/dcdb2dfdc61676ac25676f10f71c9bba77bf81227f3e73f5c678462bffaf/futurist-3.0.0-py3-none-any.whl", hash = "sha256:645565803423c907557d59f82b2e7f33d87fd483316414466d059a0fa5aa5fc9", size = 37008, upload-time = "2024-02-23T12:20:47.54Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5b/a4418215b594fa44dea7deae61fa406139e2e8acc6442d25f93d80c52c84/futurist-3.2.1-py3-none-any.whl", hash = "sha256:c76a1e7b2c6b264666740c3dffbdcf512bd9684b4b253a3068a0135b43729745", size = 40485, upload-time = "2025-08-29T15:06:56.476Z" }, ] [[package]] @@ -365,14 +393,15 @@ wheels = [ [[package]] name = "ironic" -version = "29.0.3" +version = "32.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, { name = "automaton" }, { name = "bcrypt" }, + { name = "cheroot" }, { name = "construct" }, - { name = "eventlet" }, + { name = "cotyledon" }, { name = "futurist" }, { name = "jinja2" }, { name = "jsonpatch" }, @@ -392,7 +421,6 @@ dependencies = [ { name = "oslo-messaging" }, { name = "oslo-middleware" }, { name = "oslo-policy" }, - { name = "oslo-rootwrap" }, { name = "oslo-serialization" }, { name = "oslo-service" }, { name = "oslo-upgradecheck" }, @@ -415,9 +443,9 @@ dependencies = [ { name = "websockify" }, { name = "zeroconf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/3d/bb424b088a9fa50bacd762d5deb24a72c37356c8e09f113017c609d9e904/ironic-29.0.3.tar.gz", hash = "sha256:9824c7f8526f4eccb9e093b3c4e76ab7c0d73f301867b4cce0a8bf6c0f789a38", size = 2868018, upload-time = "2025-06-21T13:01:29.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/ff/0d28d00d1b565c25e155d895226f0bb03b06ea7f6be94d3753a77015038d/ironic-32.0.0.tar.gz", hash = "sha256:92cacdfb6793c3ce39e3ab808b6b7e890a710a49816e1faab49950603b2f2cea", size = 2969350, upload-time = "2025-09-11T12:45:06.984Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/a1/becda69168ff02afbfb2f962935faadebe20dbae7b8122c88c7de849ad22/ironic-29.0.3-py3-none-any.whl", hash = "sha256:f20e5bb5cf55f1a670d16ad0d331bf087b92c100432ed0d22c7d64892d2c3cce", size = 2266735, upload-time = "2025-06-21T13:01:26.884Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6e/4204df18b448d029882e00a3e936537a3bdc16900adceb6eb9d50e118f6a/ironic-32.0.0-py3-none-any.whl", hash = "sha256:fae4bb972e108d854786e1816d273bce3cc8aa72d117d9d2d9d9348b2ce875fc", size = 2332148, upload-time = "2025-09-11T12:45:05.084Z" }, ] [[package]] @@ -439,7 +467,7 @@ test = [ [package.metadata] requires-dist = [ - { name = "ironic", specifier = ">=29.0,<30" }, + { name = "ironic", specifier = ">=32.0,<33" }, { name = "pyyaml", specifier = "~=6.0" }, { name = "understack-flavor-matcher", directory = "../understack-flavor-matcher" }, ] @@ -460,16 +488,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6c/0c/f37b6a241f0759b7653ffa7213889d89ad49a2b76eb2ddf3b57b2738c347/iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242", size = 7545, upload-time = "2023-10-03T00:25:32.304Z" }, ] +[[package]] +name = "jaraco-functools" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/ed/1aa2d585304ec07262e1a83a9889880701079dde796ac7b1d1826f40c63d/jaraco_functools-4.3.0.tar.gz", hash = "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294", size = 19755, upload-time = "2025-08-18T20:05:09.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/09/726f168acad366b11e420df31bf1c702a54d373a83f968d94141a8c3fde0/jaraco_functools-4.3.0-py3-none-any.whl", hash = "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", size = 10408, upload-time = "2025-08-18T20:05:08.69Z" }, +] + [[package]] name = "jinja2" -version = "3.1.5" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674, upload-time = "2024-12-21T18:30:22.828Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596, upload-time = "2024-12-21T18:30:19.133Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] @@ -646,6 +686,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/23/5859fad50fc5a985d55adc53f629ed4ff578be2d5df9d271fbfa5929decb/microversion_parse-2.0.0-py3-none-any.whl", hash = "sha256:c9bf9665ad65be8da8a7321e403fbf9ada892e4b4fbbc168395fac6f1f1a17ee", size = 19611, upload-time = "2024-08-29T15:38:34.79Z" }, ] +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + [[package]] name = "msgpack" version = "1.1.0" @@ -941,15 +990,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/5c/bdd892306eba5dff37895cc13ba3bb93dd0714bff4e8e86946284ea9e505/oslo.policy-4.5.0-py3-none-any.whl", hash = "sha256:d78bd90bd829f1a7896521d539eca30bfb634154a3754f0f744e7b6b6bec9c4b", size = 88579, upload-time = "2024-11-07T07:35:51.9Z" }, ] -[[package]] -name = "oslo-rootwrap" -version = "7.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/e5/8b6f29746b873dc74b4723e7fcef1b87951c848e6d3fdc5484c30237302b/oslo.rootwrap-7.4.0.tar.gz", hash = "sha256:4450fe799ca4a1c5d9e1d68875da891d0e4f8f14f08aa260ad43104b33a2bfd8", size = 50246, upload-time = "2024-11-19T11:23:02.312Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/be/29d7da8dfd9b6ad46957121ea70493bd72615fdd135c52589e7fdc6fa960/oslo.rootwrap-7.4.0-py3-none-any.whl", hash = "sha256:0ef3f4e6dfd81909ebfb6495ce120a6555590f22c715b1fabe2c9e322e3dd594", size = 38160, upload-time = "2024-11-19T11:23:01.235Z" }, -] - [[package]] name = "oslo-serialization" version = "5.6.0" @@ -967,7 +1007,7 @@ wheels = [ [[package]] name = "oslo-service" -version = "3.6.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -984,9 +1024,9 @@ dependencies = [ { name = "webob" }, { name = "yappi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/57/6b9544ecf6a919e6580567b6506ae427306ed4c7528a86c377e976abab7c/oslo.service-3.6.0.tar.gz", hash = "sha256:32487367bb1c51c0654618021c3754a6a081151a2800d96eccfce4912356a63a", size = 86583, upload-time = "2024-11-14T09:25:22.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/ea/be7735dd5e5f8e020b4559edac02c10faba1bac8388a5d6c6f3d8fb87687/oslo_service-4.3.0.tar.gz", hash = "sha256:7d856beee4c860a39e0ad5b2722882ba9f20eabf7fb29f8fdc86db2f7a5532e2", size = 105937, upload-time = "2025-08-28T09:51:42.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/a7/9cc4d4eb6c1d4be315bc0ba78e627a4bdc6d045996f26791ad872db0a874/oslo.service-3.6.0-py3-none-any.whl", hash = "sha256:3a60eba55950c9fb16e804bf9d48073f626c1f7a0b1a0b32ac821d1a958af751", size = 77007, upload-time = "2024-11-14T09:25:20.664Z" }, + { url = "https://files.pythonhosted.org/packages/85/04/009b035105b62524af4b9ec99b13334aec3d612f962e168eac2e4c6881f7/oslo_service-4.3.0-py3-none-any.whl", hash = "sha256:00e73b949cfcbe3c335cead78d6f0905afe46e8e482af0e3b75fa6fab43c27c5", size = 101220, upload-time = "2025-08-28T09:51:37.793Z" }, ] [[package]] @@ -1440,6 +1480,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/2e/c5c1689e80298d4e94c75b70faada4c25445739d91b94c211244a3ed7ed1/rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d", size = 233338, upload-time = "2024-12-04T15:33:51.954Z" }, ] +[[package]] +name = "setproctitle" +version = "1.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/48/49393a96a2eef1ab418b17475fb92b8fcfad83d099e678751b05472e69de/setproctitle-1.3.7.tar.gz", hash = "sha256:bc2bc917691c1537d5b9bca1468437176809c7e11e5694ca79a9ca12345dcb9e", size = 27002, upload-time = "2025-09-05T12:51:25.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/48/fb401ec8c4953d519d05c87feca816ad668b8258448ff60579ac7a1c1386/setproctitle-1.3.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cf555b6299f10a6eb44e4f96d2f5a3884c70ce25dc5c8796aaa2f7b40e72cb1b", size = 18079, upload-time = "2025-09-05T12:49:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a3/c2b0333c2716fb3b4c9a973dd113366ac51b4f8d56b500f4f8f704b4817a/setproctitle-1.3.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:690b4776f9c15aaf1023bb07d7c5b797681a17af98a4a69e76a1d504e41108b7", size = 13099, upload-time = "2025-09-05T12:49:09.222Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f8/17bda581c517678260e6541b600eeb67745f53596dc077174141ba2f6702/setproctitle-1.3.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:00afa6fc507967d8c9d592a887cdc6c1f5742ceac6a4354d111ca0214847732c", size = 31793, upload-time = "2025-09-05T12:49:10.297Z" }, + { url = "https://files.pythonhosted.org/packages/27/d1/76a33ae80d4e788ecab9eb9b53db03e81cfc95367ec7e3fbf4989962fedd/setproctitle-1.3.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e02667f6b9fc1238ba753c0f4b0a37ae184ce8f3bbbc38e115d99646b3f4cd3", size = 32779, upload-time = "2025-09-05T12:49:12.157Z" }, + { url = "https://files.pythonhosted.org/packages/59/27/1a07c38121967061564f5e0884414a5ab11a783260450172d4fc68c15621/setproctitle-1.3.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:83fcd271567d133eb9532d3b067c8a75be175b2b3b271e2812921a05303a693f", size = 34578, upload-time = "2025-09-05T12:49:13.393Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d4/725e6353935962d8bb12cbf7e7abba1d0d738c7f6935f90239d8e1ccf913/setproctitle-1.3.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13fe37951dda1a45c35d77d06e3da5d90e4f875c4918a7312b3b4556cfa7ff64", size = 32030, upload-time = "2025-09-05T12:49:15.362Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/e4677ae8e1cb0d549ab558b12db10c175a889be0974c589c428fece5433e/setproctitle-1.3.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a05509cfb2059e5d2ddff701d38e474169e9ce2a298cf1b6fd5f3a213a553fe5", size = 33363, upload-time = "2025-09-05T12:49:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/55/d4/69ce66e4373a48fdbb37489f3ded476bb393e27f514968c3a69a67343ae0/setproctitle-1.3.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6da835e76ae18574859224a75db6e15c4c2aaa66d300a57efeaa4c97ca4c7381", size = 31508, upload-time = "2025-09-05T12:49:18.032Z" }, + { url = "https://files.pythonhosted.org/packages/34/8a/aff5506ce89bc3168cb492b18ba45573158d528184e8a9759a05a09088a9/setproctitle-1.3.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:eb440c5644a448e6203935ed60466ec8d0df7278cd22dc6cf782d07911bcbea6", size = 12654, upload-time = "2025-09-05T12:51:17.141Z" }, + { url = "https://files.pythonhosted.org/packages/41/89/5b6f2faedd6ced3d3c085a5efbd91380fb1f61f4c12bc42acad37932f4e9/setproctitle-1.3.7-pp310-pypy310_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:502b902a0e4c69031b87870ff4986c290ebbb12d6038a70639f09c331b18efb2", size = 14284, upload-time = "2025-09-05T12:51:18.393Z" }, +] + [[package]] name = "setuptools" version = "75.7.0" @@ -1502,7 +1560,7 @@ wheels = [ [[package]] name = "sushy" -version = "5.3.0" +version = "5.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pbr" }, @@ -1510,9 +1568,9 @@ dependencies = [ { name = "requests" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/b1/9be559d65e0932b3373fcd49a1f980fb43a9c8666bf2de4a07cf5d5975da/sushy-5.3.0.tar.gz", hash = "sha256:8785c4febf227b002750f316e856f31e894448fdbda816658aba201983f37e82", size = 236861, upload-time = "2024-10-24T08:07:59.634Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/67/509895511384ddcb329aced51aca63e885289dfbb3c30a8b35745e907bb0/sushy-5.7.1.tar.gz", hash = "sha256:f593efb425c7a1d42e5078c1001a5ec8aba9f2271a400023346f36acebbaf95e", size = 280062, upload-time = "2025-08-21T09:33:05.509Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/75/37034cd517171054f643d7a9ec36f3cc5d8feee7169da8dca0669d27332c/sushy-5.3.0-py3-none-any.whl", hash = "sha256:4b02d98cef30c9842e024ca42ca0894af07e5fab5ec95b1f734b3e302aca8339", size = 349115, upload-time = "2024-10-24T08:07:57.867Z" }, + { url = "https://files.pythonhosted.org/packages/a9/36/786864aa1c0e598888fb3e4a686984cc95738e7818dcf97d3fe95e566a31/sushy-5.7.1-py3-none-any.whl", hash = "sha256:dd590781229cfe948499bfab5b42a6afc8529f04cef4eacfb7814304453331ce", size = 420664, upload-time = "2025-08-21T09:33:04.319Z" }, ] [[package]] diff --git a/python/neutron-understack/pyproject.toml b/python/neutron-understack/pyproject.toml index 8cf44be5e..b3efe6def 100644 --- a/python/neutron-understack/pyproject.toml +++ b/python/neutron-understack/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "requests>=2,<3", "neutron-lib>=3,<4", - "neutron>=26,<27", + "neutron>=27,<28", "pynautobot>=2.6.1,<3", ] diff --git a/python/neutron-understack/uv.lock b/python/neutron-understack/uv.lock index 53e785f95..4dab1c24a 100644 --- a/python/neutron-understack/uv.lock +++ b/python/neutron-understack/uv.lock @@ -181,6 +181,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "cotyledon" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setproctitle", marker = "sys_platform != 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/15/4f47ad3c352568d5adf9aec06279b1fa3a12a02e02da343dfae0512f2403/cotyledon-2.1.0.tar.gz", hash = "sha256:ddf5d3639efd10789c9708a89890e55529b860986e7ca7d602a13c9eb15d8709", size = 26754, upload-time = "2025-08-30T18:56:04.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/14/baf084e49f818049e111bd221075aa54caa71be134052e880c989efd55ad/cotyledon-2.1.0-py3-none-any.whl", hash = "sha256:6b32f069300832b24a3e1a69ced003d8ed725549e38a8a98adb342864adf17cf", size = 25326, upload-time = "2025-08-30T18:56:03.247Z" }, +] + [[package]] name = "coverage" version = "7.10.7" @@ -333,11 +345,14 @@ wheels = [ [[package]] name = "futurist" -version = "3.0.0" +version = "3.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/24/864408313afba48440ee3a560e1a70b62b39e6c0dfeddea9506699e6e606/futurist-3.0.0.tar.gz", hash = "sha256:6422011792414c39228e114bec5494303aaf06dcd335e4f8dd4f907f78a41f79", size = 44836, upload-time = "2024-02-23T12:20:49.61Z" } +dependencies = [ + { name = "debtcollector" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/12/786f4aaf9d396d67b1b7b90f248ff994e916605d0751d08a0344a4a785a6/futurist-3.2.1.tar.gz", hash = "sha256:01dd4f30acdfbb2e2eb6091da565eded82d8cbaf6c48a36cc7f73c11cfa7fb3f", size = 49326, upload-time = "2025-08-29T15:06:57.733Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/2b/dcdb2dfdc61676ac25676f10f71c9bba77bf81227f3e73f5c678462bffaf/futurist-3.0.0-py3-none-any.whl", hash = "sha256:645565803423c907557d59f82b2e7f33d87fd483316414466d059a0fa5aa5fc9", size = 37008, upload-time = "2024-02-23T12:20:47.54Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5b/a4418215b594fa44dea7deae61fa406139e2e8acc6442d25f93d80c52c84/futurist-3.2.1-py3-none-any.whl", hash = "sha256:c76a1e7b2c6b264666740c3dffbdcf512bd9684b4b253a3068a0135b43729745", size = 40485, upload-time = "2025-08-29T15:06:56.476Z" }, ] [[package]] @@ -640,7 +655,7 @@ wheels = [ [[package]] name = "neutron" -version = "26.0.1" +version = "27.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -672,7 +687,7 @@ dependencies = [ { name = "oslo-reports" }, { name = "oslo-rootwrap" }, { name = "oslo-serialization" }, - { name = "oslo-service" }, + { name = "oslo-service", extra = ["threading"] }, { name = "oslo-upgradecheck" }, { name = "oslo-utils" }, { name = "oslo-versionedobjects" }, @@ -697,14 +712,14 @@ dependencies = [ { name = "tooz" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/73/8052fa00e981ada6dc3ff7897758c7bd9e729d9766e9831244697c840e21/neutron-26.0.1.tar.gz", hash = "sha256:6e1633640ea65d359e1f1a2f5c88ea4beb92deb7bb64f3fbe6790cd47d4db01f", size = 12215939, upload-time = "2025-06-13T15:05:35.963Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/cb/1b4d9490b379162549d65091c9583920266ccbd93ecc1da7dd5db4cb2f98/neutron-27.0.0.tar.gz", hash = "sha256:c851ab6bf5792a713d43d8900c9a0751c75d7ccf1b0eb7c452b6d17fec1bec15", size = 12225381, upload-time = "2025-10-01T10:53:28.397Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/6a/9681db9c9897d00e0135c3f253527fa4e0c65f2367a3574954dd41a5e615/neutron-26.0.1-py3-none-any.whl", hash = "sha256:04d838052adbe3b1c4861a30cba9ae54dabc857d2c5a139dde73cbea6b19c51b", size = 4333219, upload-time = "2025-06-13T15:05:33.498Z" }, + { url = "https://files.pythonhosted.org/packages/43/00/3353b5ee7e427fab59d8c5d98e42fb130446c298e228190f1613a9ae4496/neutron-27.0.0-py3-none-any.whl", hash = "sha256:b6f9bd0ec8c3127d54cf96a9a475fcc15f8bbff3437b3f20a1b3cf8a8073dafe", size = 4342183, upload-time = "2025-10-01T10:53:14.997Z" }, ] [[package]] name = "neutron-lib" -version = "3.18.2" +version = "3.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -732,9 +747,9 @@ dependencies = [ { name = "stevedore" }, { name = "webob" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/56/f44432a31a3bcfea3f11c66d08ec2b1b30209ad258f8398e91875bc61b58/neutron_lib-3.18.2.tar.gz", hash = "sha256:faa9d54c164b32d8ac773f3031abb050836cda9a6483479762726a8490bd2fb0", size = 549047, upload-time = "2025-02-27T22:45:18.754Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/07/c3b31378730d8cde3109361e096f90536452808e4a122d4fe7f96b204660/neutron_lib-3.22.0.tar.gz", hash = "sha256:c8f5f9344027f1ac54e6c9f189307e79a9d6bb3470bba70ab949e6acc5c36e9d", size = 549552, upload-time = "2025-10-09T10:15:14.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/05/5d0ffebd6e95cba6a56be047e96cf4ea8bff2941f82c413fdbcd14567685/neutron_lib-3.18.2-py3-none-any.whl", hash = "sha256:5f5a6ef61f2bedf735d5a6d22f3987e3415a3f5a06acdc52830c4f7037c75476", size = 620897, upload-time = "2025-02-27T22:45:17.15Z" }, + { url = "https://files.pythonhosted.org/packages/85/32/2a12bb9ba02f38beab2f0c88e3e0d0b79a96be4739527224e9bbd1802b9e/neutron_lib-3.22.0-py3-none-any.whl", hash = "sha256:136e2e781a370b715407fd8527f8a3ffb78967e6f48933b9ffa2b1121404facd", size = 623208, upload-time = "2025-10-09T10:15:12.916Z" }, ] [[package]] @@ -757,7 +772,7 @@ test = [ [package.metadata] requires-dist = [ - { name = "neutron", specifier = ">=26,<27" }, + { name = "neutron", specifier = ">=27,<28" }, { name = "neutron-lib", specifier = ">=3,<4" }, { name = "pynautobot", specifier = ">=2.6.1,<3" }, { name = "requests", specifier = ">=2,<3" }, @@ -1166,7 +1181,7 @@ wheels = [ [[package]] name = "oslo-service" -version = "3.6.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "debtcollector" }, @@ -1183,9 +1198,15 @@ dependencies = [ { name = "webob" }, { name = "yappi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/57/6b9544ecf6a919e6580567b6506ae427306ed4c7528a86c377e976abab7c/oslo.service-3.6.0.tar.gz", hash = "sha256:32487367bb1c51c0654618021c3754a6a081151a2800d96eccfce4912356a63a", size = 86583, upload-time = "2024-11-14T09:25:22.699Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/ea/be7735dd5e5f8e020b4559edac02c10faba1bac8388a5d6c6f3d8fb87687/oslo_service-4.3.0.tar.gz", hash = "sha256:7d856beee4c860a39e0ad5b2722882ba9f20eabf7fb29f8fdc86db2f7a5532e2", size = 105937, upload-time = "2025-08-28T09:51:42.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/a7/9cc4d4eb6c1d4be315bc0ba78e627a4bdc6d045996f26791ad872db0a874/oslo.service-3.6.0-py3-none-any.whl", hash = "sha256:3a60eba55950c9fb16e804bf9d48073f626c1f7a0b1a0b32ac821d1a958af751", size = 77007, upload-time = "2024-11-14T09:25:20.664Z" }, + { url = "https://files.pythonhosted.org/packages/85/04/009b035105b62524af4b9ec99b13334aec3d612f962e168eac2e4c6881f7/oslo_service-4.3.0-py3-none-any.whl", hash = "sha256:00e73b949cfcbe3c335cead78d6f0905afe46e8e482af0e3b75fa6fab43c27c5", size = 101220, upload-time = "2025-08-28T09:51:37.793Z" }, +] + +[package.optional-dependencies] +threading = [ + { name = "cotyledon" }, + { name = "futurist" }, ] [[package]] diff --git a/workflows/argo-events/workflowtemplates/alert-automation-neutron-agent-down.yaml b/workflows/argo-events/workflowtemplates/alert-automation-neutron-agent-down.yaml index 4d6815a49..7c8679809 100644 --- a/workflows/argo-events/workflowtemplates/alert-automation-neutron-agent-down.yaml +++ b/workflows/argo-events/workflowtemplates/alert-automation-neutron-agent-down.yaml @@ -23,7 +23,7 @@ spec: - name: id - name: hostname container: - image: ghcr.io/rackerlabs/understack/openstack-client:2025.1-ubuntu_jammy + image: ghcr.io/rackerlabs/understack/openstack-client:2025.2-ubuntu_jammy command: ["sh", "-c"] args: - | diff --git a/workflows/argo-events/workflowtemplates/enroll-server.yaml b/workflows/argo-events/workflowtemplates/enroll-server.yaml index 6cc145ffb..4ba8d1faa 100644 --- a/workflows/argo-events/workflowtemplates/enroll-server.yaml +++ b/workflows/argo-events/workflowtemplates/enroll-server.yaml @@ -121,7 +121,7 @@ spec: - name: operation - name: device_id container: - image: ghcr.io/rackerlabs/understack/openstack-client:2025.1-ubuntu_jammy + image: ghcr.io/rackerlabs/understack/openstack-client:2025.2-ubuntu_jammy command: - openstack args: @@ -150,7 +150,7 @@ spec: - name: device_id # https://rackerlabs.github.io/understack/user-guide/openstack-ironic/#setting-baremetal-node-flavor script: - image: ghcr.io/rackerlabs/understack/openstack-client:2025.1-ubuntu_jammy + image: ghcr.io/rackerlabs/understack/openstack-client:2025.2-ubuntu_jammy command: [sh] source: | echo "setting RAID1 config for node: {{inputs.parameters.device_id}}" @@ -205,7 +205,7 @@ spec: parameters: - name: device_id container: - image: ghcr.io/rackerlabs/understack/openstack-client:2025.1-ubuntu_jammy + image: ghcr.io/rackerlabs/understack/openstack-client:2025.2-ubuntu_jammy command: - openstack args: diff --git a/workflows/argo-events/workflowtemplates/reclean-server.yaml b/workflows/argo-events/workflowtemplates/reclean-server.yaml index dd8efea28..c91075eef 100644 --- a/workflows/argo-events/workflowtemplates/reclean-server.yaml +++ b/workflows/argo-events/workflowtemplates/reclean-server.yaml @@ -44,7 +44,7 @@ spec: - name: operation - name: device_id container: - image: ghcr.io/rackerlabs/understack/openstack-client:2025.1-ubuntu_jammy + image: ghcr.io/rackerlabs/understack/openstack-client:2025.2-ubuntu_jammy command: - openstack args: @@ -70,7 +70,7 @@ spec: parameters: - name: device_id container: - image: ghcr.io/rackerlabs/understack/openstack-client:2025.1-ubuntu_jammy + image: ghcr.io/rackerlabs/understack/openstack-client:2025.2-ubuntu_jammy command: - openstack args: From ea52090cd1b39d6e1755343a78b8fbaf18568a67 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Tue, 21 Oct 2025 10:31:58 -0500 Subject: [PATCH 02/11] drop pynautobot and pin urllib3 to openstack upper-constraints --- python/neutron-understack/pyproject.toml | 2 +- python/neutron-understack/uv.lock | 24 +++++------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/python/neutron-understack/pyproject.toml b/python/neutron-understack/pyproject.toml index b3efe6def..9297043f5 100644 --- a/python/neutron-understack/pyproject.toml +++ b/python/neutron-understack/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "requests>=2,<3", "neutron-lib>=3,<4", "neutron>=27,<28", - "pynautobot>=2.6.1,<3", + "urllib3==1.26.20", ] [project.entry-points."neutron.ml2.mechanism_drivers"] diff --git a/python/neutron-understack/uv.lock b/python/neutron-understack/uv.lock index 4dab1c24a..c086df03f 100644 --- a/python/neutron-understack/uv.lock +++ b/python/neutron-understack/uv.lock @@ -759,8 +759,8 @@ source = { editable = "." } dependencies = [ { name = "neutron" }, { name = "neutron-lib" }, - { name = "pynautobot" }, { name = "requests" }, + { name = "urllib3" }, ] [package.dev-dependencies] @@ -774,8 +774,8 @@ test = [ requires-dist = [ { name = "neutron", specifier = ">=27,<28" }, { name = "neutron-lib", specifier = ">=3,<4" }, - { name = "pynautobot", specifier = ">=2.6.1,<3" }, { name = "requests", specifier = ">=2,<3" }, + { name = "urllib3", specifier = "==1.26.20" }, ] [package.metadata.requires-dev] @@ -1486,20 +1486,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141, upload-time = "2022-01-07T22:06:01.861Z" }, ] -[[package]] -name = "pynautobot" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "requests" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/43/cb39f53f34b4c3bb25ca993fd7a97b08fb02c61d2b8755385f8fd43e714d/pynautobot-2.6.1.tar.gz", hash = "sha256:f8d04c47f211e7346f8a66a11d5e9943dd5cb6455da6e1c7cd802d2865f48d33", size = 31566, upload-time = "2025-02-14T22:31:53.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/ec/0d9ca9f98e00d0e8d23d4653fbf193852f8a00c82b6ef173c4356bb9b81f/pynautobot-2.6.1-py3-none-any.whl", hash = "sha256:50255265e69473be99fa4e0706e7ee5311af9f2d95d9cee7f0361b7270899d99", size = 38895, upload-time = "2025-02-14T22:31:52.102Z" }, -] - [[package]] name = "pyopenssl" version = "24.3.0" @@ -2008,11 +1994,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.3.0" +version = "1.26.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" }, + { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, ] [[package]] From a36c930150bc1377e627bcc036ad1f28337e59f7 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Tue, 21 Oct 2025 12:35:50 -0500 Subject: [PATCH 03/11] switch ironic to uwsgi --- components/ironic/configmap-ironic-bin.yaml | 25 +++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/components/ironic/configmap-ironic-bin.yaml b/components/ironic/configmap-ironic-bin.yaml index 7da23b929..052844acb 100644 --- a/components/ironic/configmap-ironic-bin.yaml +++ b/components/ironic/configmap-ironic-bin.yaml @@ -302,11 +302,28 @@ data: set -ex COMMAND="${@:-start}" + cat <<'EOF' > /etc/ironic/ironic-api-uwsgi.ini + [uwsgi] + add-header = Connection: close + buffer-size = 65535 + die-on-term = true + enable-threads = true + exit-on-reload = false + hook-master-start = unix_signal:15 gracefully_kill_them_all + http-socket = 0.0.0.0:6385 + lazy-apps = true + log-x-forwarded-for = true + master = true + processes = 1 + procname-prefix-spaced = ironic-api: + route-user-agent = ^kube-probe.* donotlog: + thunder-lock = true + worker-reload-mercy = 80 + module = ironic.wsgi:application + EOF + function start () { - exec ironic-api \ - --config-file /etc/ironic/ironic.conf \ - --config-dir /etc/ironic/ironic.conf.d \ - ${OPTIONS} + exec uwsgi --ini /etc/ironic/ironic-api-uwsgi.ini } function stop () { From 218e8fe72ce52ea38b6c6618fb123738e4cbfc72 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Tue, 18 Nov 2025 09:25:23 +0000 Subject: [PATCH 04/11] cinder: drop patch 961436 This patch has been merged and backported to 2025.2 --- containers/cinder/patches/cinder-961436.patch | 640 ------------------ containers/cinder/patches/series | 1 - 2 files changed, 641 deletions(-) delete mode 100644 containers/cinder/patches/cinder-961436.patch diff --git a/containers/cinder/patches/cinder-961436.patch b/containers/cinder/patches/cinder-961436.patch deleted file mode 100644 index e568ae75f..000000000 --- a/containers/cinder/patches/cinder-961436.patch +++ /dev/null @@ -1,640 +0,0 @@ -From 7df47080dd4326fac79cd6587f0d8caba6ad836c Mon Sep 17 00:00:00 2001 -From: jayaanand borra -Date: Sat, 07 Sep 2024 12:57:13 -0400 -Subject: [PATCH] NetApp: NVMe namespace mapping fails during VM live migration - -When new Cinder volume is created a new subsystem and -NVMe namespace created and mapped one-to-one in ONTAP. -Attach process will continue this one-to-one mapping -between subsystem and host. This approach limits -NVMe multi attach workflow and Live migration. -Single subsystem mapped to single namespace is causing -this limitation. ONTAP dosn't allow mapping -multiple subsystems to single namespace. This contrasts -with iSCSI, where multiple iGroups can be mapped -to a single LUN. To overcome this limitation, -the workflow is changed to map multiple hosts to -a single subsystem. Each subsystem then has a -one-to-one mapping with a namespace. - -Closes-bug: #2078968 -Change-Id: I90b9d74560f128e495b6ccecea2d3aa2ed68171f -(cherry picked from commit I90b9d74560f128e495b6ccecea2d3aa2ed68171f) -Signed-off-by: jayaanand borra ---- - -diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py -index d7acc74..49be832 100644 ---- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py -+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/fakes.py -@@ -3118,7 +3118,9 @@ - } - - SUBSYSTEM = 'openstack-fake_subsystem' -+SUBSYSTEM_UUID = 'fake_subsystem_uuid1' - TARGET_NQN = 'nqn.1992-01.example.com:target' -+HOST_NQN = 'nqn.1992-01.example.com:host' - GET_SUBSYSTEM_RESPONSE_REST = { - "records": [ - { -@@ -3138,7 +3140,8 @@ - "uuid": FAKE_UUID, - }, - "subsystem": { -- "name": SUBSYSTEM -+ "name": SUBSYSTEM, -+ "uuid": FAKE_UUID, - }, - "svm": { - "name": VSERVER_NAME -diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode_rest.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode_rest.py -index 9e5402c..2405940 100644 ---- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode_rest.py -+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_client_cmode_rest.py -@@ -4106,6 +4106,39 @@ - self.client.send_request.assert_called_once_with( - '/protocols/nvme/subsystems', 'get', query=query) - -+ def test_get_subsystem_by_path(self): -+ response = fake_client.GET_SUBSYSTEM_RESPONSE_REST -+ self.mock_object(self.client, 'send_request', return_value=response) -+ -+ res = self.client.get_subsystem_by_path(fake_client.NAMESPACE_NAME) -+ -+ expected_res = [{'name': fake_client.SUBSYSTEM, 'os_type': 'linux'}] -+ self.assertEqual(expected_res, res) -+ query = { -+ 'svm.name': self.client.vserver, -+ 'subsystem_maps.namespace.name': fake_client.NAMESPACE_NAME, -+ 'fields': 'name,os_type', -+ 'name': 'openstack-*', -+ } -+ self.client.send_request.assert_called_once_with( -+ '/protocols/nvme/subsystems', 'get', query=query) -+ -+ def test_get_subsystem_by_path_no_records(self): -+ response = fake_client.NO_RECORDS_RESPONSE_REST -+ self.mock_object(self.client, 'send_request', return_value=response) -+ -+ res = self.client.get_subsystem_by_path(fake_client.NAMESPACE_NAME) -+ -+ self.assertEqual([], res) -+ query = { -+ 'svm.name': self.client.vserver, -+ 'subsystem_maps.namespace.name': fake_client.NAMESPACE_NAME, -+ 'fields': 'name,os_type', -+ 'name': 'openstack-*', -+ } -+ self.client.send_request.assert_called_once_with( -+ '/protocols/nvme/subsystems', 'get', query=query) -+ - def test_create_subsystem(self): - self.mock_object(self.client, 'send_request') - -@@ -4130,12 +4163,14 @@ - - expected_res = [ - {'subsystem': fake_client.SUBSYSTEM, -+ 'subsystem_uuid': fake_client.FAKE_UUID, - 'uuid': fake_client.FAKE_UUID, - 'vserver': fake_client.VSERVER_NAME}] - self.assertEqual(expected_res, res) - query = { - 'namespace.name': fake_client.NAMESPACE_NAME, -- 'fields': 'subsystem.name,namespace.uuid,svm.name', -+ 'fields': 'subsystem.name,namespace.uuid,svm.name,' -+ 'subsystem.uuid', - } - self.client.send_request.assert_called_once_with( - '/protocols/nvme/subsystem-maps', 'get', query=query) -@@ -4216,3 +4251,102 @@ - } - self.client.send_request.assert_called_once_with( - '/protocols/nvme/subsystem-maps', 'delete', query=query) -+ -+ def test_unmap_host_with_subsystem(self): -+ url = ( -+ f'/protocols/nvme/subsystems/{fake_client.SUBSYSTEM_UUID}/' -+ f'hosts/{fake_client.HOST_NQN}' -+ ) -+ -+ self.mock_object(self.client, 'send_request') -+ -+ self.client.unmap_host_with_subsystem( -+ fake_client.HOST_NQN, fake_client.SUBSYSTEM_UUID -+ ) -+ -+ self.client.send_request.assert_called_once_with(url, 'delete') -+ -+ def test_unmap_host_with_subsystem_api_error(self): -+ url = ( -+ f'/protocols/nvme/subsystems/{fake_client.SUBSYSTEM_UUID}/' -+ f'hosts/{fake_client.HOST_NQN}' -+ ) -+ api_error = netapp_api.NaApiError(code=123, message='fake_error') -+ -+ self.mock_object(self.client, 'send_request', side_effect=api_error) -+ mock_log_warning = self.mock_object(client_cmode_rest.LOG, 'warning') -+ -+ self.client.unmap_host_with_subsystem( -+ fake_client.HOST_NQN, fake_client.SUBSYSTEM_UUID -+ ) -+ -+ self.client.send_request.assert_called_once_with(url, 'delete') -+ mock_log_warning.assert_called_once_with( -+ "Failed to unmap host from subsystem. " -+ "Host NQN: %(host_nqn)s, Subsystem UUID: %(subsystem_uuid)s, " -+ "Error Code: %(code)s, Error Message: %(message)s", -+ {'host_nqn': fake_client.HOST_NQN, -+ 'subsystem_uuid': fake_client.SUBSYSTEM_UUID, -+ 'code': api_error.code, 'message': api_error.message}) -+ -+ def test_map_host_with_subsystem(self): -+ url = f'/protocols/nvme/subsystems/{fake_client.SUBSYSTEM_UUID}/hosts' -+ body_post = {'nqn': fake_client.HOST_NQN} -+ -+ self.mock_object(self.client, 'send_request') -+ -+ self.client.map_host_with_subsystem( -+ fake_client.HOST_NQN, fake_client.SUBSYSTEM_UUID -+ ) -+ -+ self.client.send_request.assert_called_once_with( -+ url, 'post', body=body_post -+ ) -+ -+ def test_map_host_with_subsystem_already_mapped(self): -+ url = f'/protocols/nvme/subsystems/{fake_client.SUBSYSTEM_UUID}/hosts' -+ body_post = {'nqn': fake_client.HOST_NQN} -+ api_error = ( -+ netapp_api.NaApiError( -+ code=netapp_api.REST_HOST_ALREADY_MAPPED_TO_SUBSYSTEM, -+ message='fake_error') -+ ) -+ -+ self.mock_object(self.client, 'send_request', side_effect=api_error) -+ mock_log_info = self.mock_object(client_cmode_rest.LOG, 'info') -+ -+ self.client.map_host_with_subsystem( -+ fake_client.HOST_NQN, fake_client.SUBSYSTEM_UUID -+ ) -+ -+ self.client.send_request.assert_called_once_with( -+ url, 'post', body=body_post -+ ) -+ mock_log_info.assert_called_once_with( -+ "Host %(host_nqn)s is already mapped to subsystem" -+ " %(subsystem_uuid)s ", -+ {'host_nqn': fake_client.HOST_NQN, -+ 'subsystem_uuid': fake_client.SUBSYSTEM_UUID -+ } -+ ) -+ -+ def test_map_host_with_subsystem_api_error(self): -+ url = f'/protocols/nvme/subsystems/{fake_client.SUBSYSTEM_UUID}/hosts' -+ body_post = {'nqn': fake_client.HOST_NQN} -+ api_error = netapp_api.NaApiError(code=123, message='fake_error') -+ -+ self.mock_object(self.client, 'send_request', side_effect=api_error) -+ mock_log_error = self.mock_object(client_cmode_rest.LOG, 'error') -+ -+ self.assertRaises(netapp_api.NaApiError, -+ self.client.map_host_with_subsystem, -+ fake_client.HOST_NQN, fake_client.SUBSYSTEM_UUID -+ ) -+ -+ self.client.send_request.assert_called_once_with( -+ url, 'post', body=body_post -+ ) -+ mock_log_error.assert_called_once_with( -+ "Error mapping host to subsystem. Code :" -+ "%(code)s, Message: %(message)s", -+ {'code': api_error.code, 'message': api_error.message}) -diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py -index a05774a..bc6de6d 100644 ---- a/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py -+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py -@@ -1036,5 +1036,6 @@ - - REST_FIELDS = 'uuid,name,style' - SUBSYSTEM = 'openstack-fake-subsystem' -+MAPPED_SUBSYSTEM = 'openstack-fake-mapped_subsystem' - HOST_NQN = 'nqn.1992-01.example.com:string' - TARGET_NQN = 'nqn.1992-01.example.com:target' -diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nvme_library.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nvme_library.py -index 51f3ddc..075389b 100644 ---- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nvme_library.py -+++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nvme_library.py -@@ -16,7 +16,6 @@ - import copy - from unittest import mock - from unittest.mock import patch --import uuid - - import ddt - from oslo_utils import units -@@ -281,12 +280,15 @@ - self.mock_object( - self.library.client, 'get_namespace_map', - return_value=[{ -+ 'subsystem_uuid': fake.UUID1, - 'subsystem': fake.SUBSYSTEM, - 'uuid': fake.UUID1 - }]) - -- subsystem, n_uuid = self.library._find_mapped_namespace_subsystem( -- fake.NAMESPACE_NAME, fake.HOST_NQN) -+ subsystem_uuid, subsystem, n_uuid =\ -+ self.library._find_mapped_namespace_subsystem( -+ fake.NAMESPACE_NAME, fake.HOST_NQN -+ ) - - self.assertEqual(fake.SUBSYSTEM, subsystem) - self.assertEqual(fake.UUID1, n_uuid) -@@ -598,7 +600,7 @@ - 'consistent_group_snapshot_enabled': False, - 'reserved_percentage': 5, - 'max_over_subscription_ratio': 10, -- 'multiattach': False, -+ 'multiattach': True, - 'total_capacity_gb': 10.0, - 'free_capacity_gb': 2.0, - 'netapp_dedupe_used_percent': 55.0, -@@ -790,44 +792,31 @@ - self.library.client.namespace_resize.assert_called_once_with( - fake.PATH_NAMESPACE, new_bytes) - -- @ddt.data([{'name': fake.SUBSYSTEM, 'os_type': 'linux'}], []) -- def test__get_or_create_subsystem(self, subs): -- self.mock_object(self.library.client, 'get_subsystem_by_host', -- return_value=subs) -- self.mock_object(self.library.client, 'create_subsystem') -- self.mock_object(uuid, 'uuid4', return_value='fake_uuid') -- -- sub, os = self.library._get_or_create_subsystem(fake.HOST_NQN, 'linux') -- -- self.library.client.get_subsystem_by_host.assert_called_once_with( -- fake.HOST_NQN) -- self.assertEqual('linux', os) -- if subs: -- self.assertEqual(fake.SUBSYSTEM, sub) -- else: -- self.library.client.create_subsystem.assert_called_once_with( -- sub, 'linux', fake.HOST_NQN) -- expected_sub = 'openstack-fake_uuid' -- self.assertEqual(expected_sub, sub) -- - def test__map_namespace(self): - self.library.host_type = 'win' -- self.mock_object(self.library, '_get_or_create_subsystem', -- return_value=(fake.SUBSYSTEM, 'linux')) -+ fake_namespace_metadata = [{ -+ 'subsystem': 'fake_subsystem', -+ 'subsystem_uuid': 'fake_subsystem_uuid', -+ 'uuid': 'fake_uuid' -+ }] -+ - self.mock_object(self.library, '_get_namespace_attr', - return_value=fake.NAMESPACE_METADATA) - self.mock_object(self.library.client, 'map_namespace', - return_value=fake.UUID1) -+ self.mock_object(self.library.client, 'get_namespace_map', -+ return_value=fake_namespace_metadata) - -- sub, n_uuid = self.library._map_namespace( -- fake.NAMESPACE_NAME, fake.HOST_NQN) -+ host_nqn = 'fake_host_nqn' -+ name = 'fake_namespace_name' - -- self.assertEqual(fake.SUBSYSTEM, sub) -- self.assertEqual(fake.UUID1, n_uuid) -- self.library._get_or_create_subsystem.assert_called_once_with( -- fake.HOST_NQN, 'win') -- self.library.client.map_namespace.assert_called_once_with( -- fake.PATH_NAMESPACE, fake.SUBSYSTEM) -+ subsystem_name, ns_uuid = self.library._map_namespace(name, host_nqn) -+ -+ self.assertEqual(subsystem_name, 'fake_subsystem') -+ self.assertEqual(ns_uuid, 'fake_uuid') -+ self.library.client.map_host_with_subsystem.assert_called_once_with( -+ host_nqn, 'fake_subsystem_uuid' -+ ) - - def test_initialize_connection(self): - self.mock_object(self.library, '_map_namespace', -@@ -899,7 +888,7 @@ - def test__unmap_namespace(self, host_nqn): - mock_find = self.mock_object( - self.library, '_find_mapped_namespace_subsystem', -- return_value=(fake.SUBSYSTEM, 'fake')) -+ return_value=(fake.UUID1, fake.SUBSYSTEM, 'fake')) - self.mock_object(self.library.client, 'get_namespace_map', - return_value=[{'subsystem': fake.SUBSYSTEM}]) - self.mock_object(self.library.client, 'unmap_namespace') -@@ -912,10 +901,6 @@ - self.library.client.get_namespace_map.assert_not_called() - else: - self.library._find_mapped_namespace_subsystem.assert_not_called() -- self.library.client.get_namespace_map.assert_called_once_with( -- fake.PATH_NAMESPACE) -- self.library.client.unmap_namespace.assert_called_once_with( -- fake.PATH_NAMESPACE, fake.SUBSYSTEM) - - @ddt.data(None, {'nqn': fake.HOST_NQN}) - def test_terminate_connection(self, connector): -diff --git a/cinder/volume/drivers/netapp/dataontap/client/api.py b/cinder/volume/drivers/netapp/dataontap/client/api.py -index 20121c3..d033728 100644 ---- a/cinder/volume/drivers/netapp/dataontap/client/api.py -+++ b/cinder/volume/drivers/netapp/dataontap/client/api.py -@@ -660,6 +660,7 @@ - REST_NO_SUCH_LUN_MAP = '5374922' - REST_NO_SUCH_FILE = '6684674' - REST_NAMESPACE_EOBJECTNOTFOUND = ('72090006', '72090006') -+REST_HOST_ALREADY_MAPPED_TO_SUBSYSTEM = '72089705' - - - class RestNaServer(object): -diff --git a/cinder/volume/drivers/netapp/dataontap/client/client_cmode_rest.py b/cinder/volume/drivers/netapp/dataontap/client/client_cmode_rest.py -index edd6300..3c30ffa 100644 ---- a/cinder/volume/drivers/netapp/dataontap/client/client_cmode_rest.py -+++ b/cinder/volume/drivers/netapp/dataontap/client/client_cmode_rest.py -@@ -2799,6 +2799,22 @@ - return [{'name': subsystem['name'], 'os_type': subsystem['os_type']} - for subsystem in records] - -+ def get_subsystem_by_path(self, path): -+ """Get subsystem by its namespace path.""" -+ query = { -+ 'svm.name': self.vserver, -+ 'subsystem_maps.namespace.name': path, -+ 'fields': 'name,os_type', -+ 'name': f'{na_utils.OPENSTACK_PREFIX}*', -+ } -+ response = self.send_request('/protocols/nvme/subsystems', 'get', -+ query=query) -+ -+ records = response.get('records', []) -+ -+ return [{'name': subsystem['name'], 'os_type': subsystem['os_type']} -+ for subsystem in records] -+ - def create_subsystem(self, subsystem_name, os_type, host_nqn): - """Creates subsystem with specified args.""" - body = { -@@ -2813,7 +2829,7 @@ - """Gets the namespace map using its path.""" - query = { - 'namespace.name': path, -- 'fields': 'subsystem.name,namespace.uuid,svm.name', -+ 'fields': 'subsystem.name,namespace.uuid,svm.name,subsystem.uuid', - } - response = self.send_request('/protocols/nvme/subsystem-maps', - 'get', -@@ -2824,6 +2840,7 @@ - for map in records: - map_subsystem = {} - map_subsystem['subsystem'] = map['subsystem']['name'] -+ map_subsystem['subsystem_uuid'] = map['subsystem']['uuid'] - map_subsystem['uuid'] = map['namespace']['uuid'] - map_subsystem['vserver'] = map['svm']['name'] - -@@ -2895,3 +2912,54 @@ - } - self.send_request('/protocols/nvme/subsystem-maps', 'delete', - query=query) -+ -+ def unmap_host_with_subsystem(self, host_nqn, subsystem_uuid): -+ """Unmaps a host from given subsystem. -+ -+ In multiattach and live migration scenarios,it is possible that the -+ host is attached to single namespace from different subsystems and -+ repeated unmapping to subsystem to host is possible. Errors are -+ logged but not propagated. Calling code will proceed even if -+ unmapping fails. -+ """ -+ url = f'/protocols/nvme/subsystems/{subsystem_uuid}/hosts/{host_nqn}' -+ try: -+ self.send_request(url, 'delete') -+ except netapp_api.NaApiError as e: -+ LOG.warning( -+ "Failed to unmap host from subsystem. " -+ "Host NQN: %(host_nqn)s, Subsystem UUID: %(subsystem_uuid)s, " -+ "Error Code: %(code)s, Error Message: %(message)s", -+ {'host_nqn': host_nqn, 'subsystem_uuid': subsystem_uuid, -+ 'code': e.code, 'message': e.message} -+ ) -+ -+ def map_host_with_subsystem(self, host_nqn, subsystem_uuid): -+ """Add host nqn to the subsystem""" -+ -+ body_post = { -+ 'nqn': host_nqn, -+ } -+ try: -+ self.send_request( -+ f'/protocols/nvme/subsystems/{subsystem_uuid}/hosts', -+ 'post', -+ body=body_post -+ ) -+ except netapp_api.NaApiError as e: -+ code = e.code -+ message = e.message -+ if e.code == netapp_api.REST_HOST_ALREADY_MAPPED_TO_SUBSYSTEM: -+ LOG.info( -+ 'Host %(host_nqn)s is already mapped to subsystem ' -+ '%(subsystem_uuid)s ', {'host_nqn': host_nqn, -+ 'subsystem_uuid': subsystem_uuid -+ } -+ ) -+ else: -+ LOG.error( -+ 'Error mapping host to subsystem. Code :' -+ '%(code)s, Message: %(message)s', -+ {'code': code, 'message': message} -+ ) -+ raise -diff --git a/cinder/volume/drivers/netapp/dataontap/nvme_library.py b/cinder/volume/drivers/netapp/dataontap/nvme_library.py -index 0de826f9..5abb9b3 100644 ---- a/cinder/volume/drivers/netapp/dataontap/nvme_library.py -+++ b/cinder/volume/drivers/netapp/dataontap/nvme_library.py -@@ -524,7 +524,7 @@ - - # Add driver capabilities and config info - pool['QoS_support'] = False -- pool['multiattach'] = False -+ pool['multiattach'] = True - pool['online_extend_support'] = False - pool['consistencygroup_support'] = False - pool['consistent_group_snapshot_enabled'] = False -@@ -610,67 +610,46 @@ - - self.namespace_table[name].size = new_size_bytes - -- def _get_or_create_subsystem(self, host_nqn, host_os_type): -- """Checks for an subsystem for a host. -- -- Creates subsystem if not already present with given host os type and -- adds the host. -- """ -- # Backend supports different subsystems with the same hosts, so -- # instead of reusing non OpenStack subsystem, we make sure we only use -- # our own, thus being compatible with custom subsystem. -- subsystems = self.client.get_subsystem_by_host( -- host_nqn) -- if subsystems: -- subsystem_name = subsystems[0]['name'] -- host_os_type = subsystems[0]['os_type'] -- else: -- subsystem_name = na_utils.OPENSTACK_PREFIX + str(uuid.uuid4()) -- self.client.create_subsystem(subsystem_name, host_os_type, -- host_nqn) -- -- return subsystem_name, host_os_type -- - def _find_mapped_namespace_subsystem(self, path, host_nqn): - """Find an subsystem for a namespace mapped to the given host.""" - subsystems = [subsystem['name'] for subsystem in - self.client.get_subsystem_by_host(host_nqn)] - - # Map subsystem name to namespace-id for the requested host. -- namespace_map = {v['subsystem']: v['uuid'] -+ namespace_map = {v['uuid']: (v['subsystem_uuid'], v['subsystem']) - for v in self.client.get_namespace_map(path) - if v['subsystem'] in subsystems} - -- subsystem_name = n_uuid = None -+ subsystem_uuid = subsystem_name = n_uuid = None - # Give preference to OpenStack subsystems, just use the last one if not - # present to allow unmapping old mappings that used a custom subsystem. -- for subsystem_name, n_uuid in namespace_map.items(): -+ for n_uuid, (subsystem_uuid, subsystem_name) in namespace_map.items(): - if subsystem_name.startswith(na_utils.OPENSTACK_PREFIX): - break - -- return subsystem_name, n_uuid -+ return subsystem_uuid, subsystem_name, n_uuid - - def _map_namespace(self, name, host_nqn): - """Maps namespace to the host nqn and returns its ID assigned.""" -- -- subsystem_name, subsystem_host_os = self._get_or_create_subsystem( -- host_nqn, self.host_type) -- if subsystem_host_os != self.host_type: -- LOG.warning("Namespace misalignment may occur for current" -- " subsystem %(sub_name)s with host OS type" -- " %(sub_os)s. Please configure subsystem manually" -- " according to the type of the host OS.", -- {'sub_name': subsystem_name, -- 'sub_os': subsystem_host_os}) -- - metadata = self._get_namespace_attr(name, 'metadata') - path = metadata['Path'] - try: -- ns_uuid = self.client.map_namespace( -- path, subsystem_name,) -+ subsystems = self.client.get_namespace_map(path) -+ ns_uuid = subsystem_uuid = None -+ if subsystems: -+ subsystem_name = subsystems[0]['subsystem'] -+ subsystem_uuid = subsystems[0]['subsystem_uuid'] -+ ns_uuid = subsystems[0]['uuid'] -+ self.client.map_host_with_subsystem(host_nqn, subsystem_uuid) -+ else: -+ subsystem_name = na_utils.OPENSTACK_PREFIX + str(uuid.uuid4()) -+ self.client.create_subsystem(subsystem_name, self.host_type, -+ host_nqn) -+ ns_uuid = self.client.map_namespace(path, subsystem_name, ) - return subsystem_name, ns_uuid - except netapp_api.NaApiError as e: -- (subsystem_name, ns_uuid) = self._find_mapped_namespace_subsystem( -+ (_, subsystem_name, ns_uuid) =\ -+ self._find_mapped_namespace_subsystem( - path, host_nqn) - if ns_uuid is not None and subsystem_name: - return subsystem_name, ns_uuid -@@ -737,18 +716,18 @@ - def _unmap_namespace(self, path, host_nqn): - """Unmaps a namespace from given host.""" - -- namespace_unmap_list = [] -- if host_nqn: -- (subsystem, _) = self._find_mapped_namespace_subsystem( -- path, host_nqn) -- namespace_unmap_list.append((path, subsystem)) -- else: -- namespace_maps = self.client.get_namespace_map(path) -- namespace_unmap_list = [ -- (path, m['subsystem']) for m in namespace_maps] -+ if not host_nqn: -+ LOG.warning("Nothing to unmap - host_nqn is missing: %s", path) -+ return - -- for _path, _subsystem in namespace_unmap_list: -- self.client.unmap_namespace(_path, _subsystem) -+ (subsystem_uuid, _, _) = self._find_mapped_namespace_subsystem( -+ path, host_nqn) -+ -+ if subsystem_uuid: -+ self.client.unmap_host_with_subsystem(host_nqn, subsystem_uuid) -+ else: -+ LOG.debug("No mapping exists between namespace: %s" -+ " and host_nqn: %s", path, host_nqn) - - @coordination.synchronized('netapp-terminate-nvme-connection-{volume.id}') - def terminate_connection(self, volume, connector, **kwargs): -diff --git a/releasenotes/notes/bug-2078968-fix-nvme-namespace-mapping-failed-during-live-migration-bbd26bb157b076bf.yaml b/releasenotes/notes/bug-2078968-fix-nvme-namespace-mapping-failed-during-live-migration-bbd26bb157b076bf.yaml -new file mode 100644 -index 0000000..a393538 ---- /dev/null -+++ b/releasenotes/notes/bug-2078968-fix-nvme-namespace-mapping-failed-during-live-migration-bbd26bb157b076bf.yaml -@@ -0,0 +1,50 @@ -+--- -+upgrade: -+ - | -+ Breaking Change: NetApp NVMe Subsystem Architecture Redesign -+ -+ Implemented a significant architectural change to NVMe volume attachment -+ handling to address critical limitations with multi-attach workflows and -+ QoS management. The previous implementation used a one-to-one mapping -+ between hosts and subsystems, where each host would have its own -+ dedicated subsystem, and multiple subsystems would map to a single -+ namespace. This approach created two major issues: -+ -+ * QoS Limitations: Since QoS policies are applied at the subsystem -+ level rather than the namespace level, having multiple subsystems -+ per namespace made it impossible to enforce consistent QoS across -+ all host connections to the same volume. -+ -+ * Multi-Attach Incompatibility: Different subsystems cannot enable -+ true multi-attach functionality, which is essential for live migration -+ and other advanced features where the same volume needs to be -+ simultaneously accessible from multiple hosts. -+ -+ New Architecture: The implementation now uses a many-to-one mapping -+ where multiple hosts share a single subsystem, ensuring a single -+ subsystem-to-namespace relationship. This resolves both QoS consistency -+ and multi-attach limitations. -+ -+ Compatibility Impact: This change is not backward compatible due to -+ fundamental differences in how NVMe subsystem-to-namespace mappings are -+ handled. Live migration of existing mappings is not technically feasible. -+ -+ Required Upgrade Path: -+ -+ * Take backup of all volumes using the old NVMe architecture -+ * Upgrade OpenStack to the version with the new architecture -+ * Restore volumes using the new many-to-one subsystem mapping model -+ * For assistance with migration planning and any questions about this -+ process, contact NetApp support who can provide guidance specific to -+ your environment and help minimize disruption during the transition. -+ -+ This approach ensures data integrity while enabling the improved -+ multi-attach and QoS capabilities of the new architecture. -+ -+fixes: -+ - | -+ NetApp Driver `Bug #2078968 -+ `_: Fixed NVMe namespace -+ mapping fails during VM migration with "Namespace is already mapped -+ to subsystem". Implemented architecture changes to support multiple -+ hosts attaching to single namespace through shared subsystem model. diff --git a/containers/cinder/patches/series b/containers/cinder/patches/series index a29a2fac4..7f2b87c24 100644 --- a/containers/cinder/patches/series +++ b/containers/cinder/patches/series @@ -1,2 +1 @@ cinder-962085.patch -cinder-961436.patch From 2cf1c907aa502b3c00f405513d236969b8fb35a8 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Tue, 18 Nov 2025 09:52:43 +0000 Subject: [PATCH 05/11] octavia: bump the chart to 2025.2 Without this, the octavia-api does not start as the https://opendev.org/openstack/openstack-helm/commit/b1d85c20e36adac9eaee584ef095d9c010cc1dc4 is needed. --- apps/openstack/octavia.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openstack/octavia.yaml b/apps/openstack/octavia.yaml index 273320998..68cae8ec9 100644 --- a/apps/openstack/octavia.yaml +++ b/apps/openstack/octavia.yaml @@ -1,4 +1,4 @@ --- component: octavia repoURL: https://tarballs.opendev.org/openstack/openstack-helm -chartVersion: 2025.1.12+80041dfbb +chartVersion: 2025.2.3+f6be959ce From a4290dc2a519101f79262daf6c25ce1458891646 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Tue, 18 Nov 2025 11:18:31 +0000 Subject: [PATCH 06/11] ironic: bump openstack helm chart --- apps/openstack/ironic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openstack/ironic.yaml b/apps/openstack/ironic.yaml index d98da29d8..f5941f8eb 100644 --- a/apps/openstack/ironic.yaml +++ b/apps/openstack/ironic.yaml @@ -1,4 +1,4 @@ --- component: ironic repoURL: https://tarballs.opendev.org/openstack/openstack-helm -chartVersion: 2025.1.3+344314dd3 +chartVersion: 2025.2.0+f6be959ce From da506a3f8e1995eb01e1027eeb09c894c7699a2f Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Thu, 20 Nov 2025 11:46:55 -0600 Subject: [PATCH 07/11] fix(neutron): increase workers and set start-time neutron's docs state that start-time needs to be defined. Increase the number of workers as well. --- components/neutron/values.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/neutron/values.yaml b/components/neutron/values.yaml index 03aecf873..d3b393e9e 100644 --- a/components/neutron/values.yaml +++ b/components/neutron/values.yaml @@ -60,6 +60,10 @@ conf: vni_ranges: "" neutron: DEFAULT: + # https://docs.openstack.org/neutron/latest/admin/config-wsgi.html + # the api_workers set the number of uWSGI processes as well + api_workers: 4 + rpc_workers: 2 # We enable the following plugins: # - 'ovn-router' enables OVN to be our L3 router. # - 'trunk' allows for us to create and configure trunk ports to allow @@ -96,6 +100,9 @@ conf: quotas: # https://github.com/openstack/neutron/blob/master/neutron/conf/quota.py#L101-L105 quota_rbac_policy: 100 + neutron_api_uwsgi: + uwsgi: + start-time: "%t" # disable the neutron-ironic-agent from loading a non-existent config pod: From 85f5d6ce0dcf6dde68920417cf44094596fdd1aa Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Mon, 24 Nov 2025 15:22:36 -0600 Subject: [PATCH 08/11] fix(ironic): increase the number of workers --- components/ironic/configmap-ironic-bin.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ironic/configmap-ironic-bin.yaml b/components/ironic/configmap-ironic-bin.yaml index 052844acb..f1a7ef0e6 100644 --- a/components/ironic/configmap-ironic-bin.yaml +++ b/components/ironic/configmap-ironic-bin.yaml @@ -314,7 +314,7 @@ data: lazy-apps = true log-x-forwarded-for = true master = true - processes = 1 + processes = 4 procname-prefix-spaced = ironic-api: route-user-agent = ^kube-probe.* donotlog: thunder-lock = true From 0cd7e86796fe870948ed257b01b4c5d7cf6f0883 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Thu, 20 Nov 2025 13:22:10 -0600 Subject: [PATCH 09/11] fix(nova): bump the number of workers --- components/nova/values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/nova/values.yaml b/components/nova/values.yaml index d3db0c638..73a9d0c5d 100644 --- a/components/nova/values.yaml +++ b/components/nova/values.yaml @@ -54,6 +54,8 @@ conf: # we aren't using this so we don't want to enable this part of the chart enabled: false DEFAULT: + # sets the number of uWSGI processes as well + osapi_compute_workers: 4 # We are not wiring up the network to the nova metadata API so we must use # config_drive to pass data. To avoid users having to remember this, just # force it on always. From 3d4e3836144bd67ae8473dd4980532a562cb9ed1 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Thu, 20 Nov 2025 15:39:31 -0600 Subject: [PATCH 10/11] fix(neutron): increase startup time for neutron When neutron starts up it is cleaning up OVN and it might take some extra time to be ready so increase the amount of time. --- components/neutron/values.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/neutron/values.yaml b/components/neutron/values.yaml index d3b393e9e..7b805d5e9 100644 --- a/components/neutron/values.yaml +++ b/components/neutron/values.yaml @@ -171,6 +171,13 @@ pod: cpu: "200m" limits: memory: "4096Mi" + probes: + server: + server: + readiness: + initialDelaySeconds: 30 + timeoutSeconds: 20 + # (nicholas.kuechler) updating the jobs list to remove the 'neutron-rabbit-init' job. dependencies: From 16a0435f2f1f5e48bd61ab9102be0da35c8f8e01 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Mon, 24 Nov 2025 15:32:09 -0600 Subject: [PATCH 11/11] fix(ironic): fix issue with image downloading without deep image inspect --- ...ect-path-to-the-image-when-deep-imag.patch | 40 +++++++++++++++++++ containers/ironic/patches/series | 1 + 2 files changed, 41 insertions(+) create mode 100644 containers/ironic/patches/0001-fix-use-the-correct-path-to-the-image-when-deep-imag.patch diff --git a/containers/ironic/patches/0001-fix-use-the-correct-path-to-the-image-when-deep-imag.patch b/containers/ironic/patches/0001-fix-use-the-correct-path-to-the-image-when-deep-imag.patch new file mode 100644 index 000000000..f0197af9a --- /dev/null +++ b/containers/ironic/patches/0001-fix-use-the-correct-path-to-the-image-when-deep-imag.patch @@ -0,0 +1,40 @@ +From 627dee659dccdb3f2e30f0454c7f73caf00705f0 Mon Sep 17 00:00:00 2001 +From: Doug Goldstein +Date: Mon, 24 Nov 2025 15:04:57 -0600 +Subject: [PATCH] fix: use the correct path to the image when deep image + inspection is off + +When deep image inspection is disabled, the incorrect path was used to +determine the image format of the file resulting in a no such file or +directory exception which bubbled up and made it appear as if the cache +was missing. + +Change-Id: Ibaf1486da9510fdad479523159797815e783e5f6 +Signed-off-by: Doug Goldstein +--- + ironic/common/images.py | 7 ++++--- + ...bug-disable-deep-image-inspection-bfd44bb8307dea1a.yaml | 7 +++++++ + 2 files changed, 11 insertions(+), 3 deletions(-) + create mode 100644 releasenotes/notes/bug-disable-deep-image-inspection-bfd44bb8307dea1a.yaml + +diff --git a/ironic/common/images.py b/ironic/common/images.py +index 7da87828f..60f4a3450 100644 +--- a/ironic/common/images.py ++++ b/ironic/common/images.py +@@ -527,12 +527,13 @@ def image_to_raw(image_href, path, path_tmp): + 'format': fmt}) + raise exception.InvalidImage() + else: +- fmt = get_source_format(image_href, path) ++ fmt = get_source_format(image_href, path_tmp) + LOG.warning("Security: Image safety checking has been disabled. " + "This is unsafe operation. Attempting to continue " +- "the detected format %(img_fmt)s for %(path)s.", ++ "with the detected format %(img_fmt)s for " ++ "image %(image_href)s.", + {'img_fmt': fmt, +- 'path': path}) ++ 'image_href': image_href}) + + if fmt not in RAW_IMAGE_FORMATS and fmt != "iso": + # When the target format is NOT raw, we need to convert it. diff --git a/containers/ironic/patches/series b/containers/ironic/patches/series index bc42a0c71..203c0662a 100644 --- a/containers/ironic/patches/series +++ b/containers/ironic/patches/series @@ -1,2 +1,3 @@ 0001-pass-along-physical_network-to-neutron-from-the-bare.patch 0001-Solve-IPMI-call-issue-results-in-UTF-8-format-error-.patch +0001-fix-use-the-correct-path-to-the-image-when-deep-imag.patch