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/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 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 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/configmap-ironic-bin.yaml b/components/ironic/configmap-ironic-bin.yaml index 7da23b929..f1a7ef0e6 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 = 4 + 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 () { 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/neutron/values.yaml b/components/neutron/values.yaml index 03aecf873..7b805d5e9 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: @@ -164,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: diff --git a/components/nova/values.yaml b/components/nova/values.yaml index 47609f6e2..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. @@ -81,7 +83,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/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 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/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..203c0662a 100644 --- a/containers/ironic/patches/series +++ b/containers/ironic/patches/series @@ -1,4 +1,3 @@ -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 +0001-fix-use-the-correct-path-to-the-image-when-deep-imag.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..9297043f5 100644 --- a/python/neutron-understack/pyproject.toml +++ b/python/neutron-understack/pyproject.toml @@ -26,8 +26,8 @@ classifiers = [ dependencies = [ "requests>=2,<3", "neutron-lib>=3,<4", - "neutron>=26,<27", - "pynautobot>=2.6.1,<3", + "neutron>=27,<28", + "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 53e785f95..c086df03f 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]] @@ -744,8 +759,8 @@ source = { editable = "." } dependencies = [ { name = "neutron" }, { name = "neutron-lib" }, - { name = "pynautobot" }, { name = "requests" }, + { name = "urllib3" }, ] [package.dev-dependencies] @@ -757,10 +772,10 @@ 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" }, + { name = "urllib3", specifier = "==1.26.20" }, ] [package.metadata.requires-dev] @@ -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]] @@ -1465,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" @@ -1987,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]] 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: