From a44cd8ef1d2579503232ca1023e99e44ab697138 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 6 Mar 2025 08:55:45 +0000 Subject: [PATCH 1/6] Update .gitreview for stable/2025.1 Change-Id: I1e53259e81c7dfba8dc07f66d4fa0bbc31a2cf41 --- .gitreview | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitreview b/.gitreview index 130a080b0..f38a02e4d 100644 --- a/.gitreview +++ b/.gitreview @@ -2,3 +2,4 @@ host=review.opendev.org port=29418 project=openstack/python-ironicclient.git +defaultbranch=stable/2025.1 From c7be7432839ecb7a80c0f50b7e88c53a95bb728f Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 6 Mar 2025 08:55:47 +0000 Subject: [PATCH 2/6] Update TOX_CONSTRAINTS_FILE for stable/2025.1 Update the URL to the upper-constraints file to point to the redirect rule on releases.openstack.org so that anyone working on this branch will switch to the correct upper-constraints list automatically when the requirements repository branches. Until the requirements repository has as stable/2025.1 branch, tests will continue to use the upper-constraints list on master. Change-Id: Id5bd2f7c6c3bb244730a13bc21cb19505161c2db --- tox.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index a3687e16b..0bbf03398 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ setenv = VIRTUAL_ENV={envdir} TESTS_DIR=./ironicclient/tests/unit usedevelop = True deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = @@ -19,7 +19,7 @@ commands = [testenv:releasenotes] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html @@ -30,7 +30,7 @@ deps = flake8-import-order>=0.17.1 # LGPLv3 pycodestyle>=2.0.0,<3.0.0 # MIT Pygments>=2.2.0 # BSD - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} commands = flake8 {posargs} doc8 doc/source CONTRIBUTING.rst README.rst @@ -47,7 +47,7 @@ commands = [testenv:venv] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt @@ -55,7 +55,7 @@ commands = {posargs} [testenv:docs] deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html From 0684eafa407973aec608fbcfac13e146511c10e2 Mon Sep 17 00:00:00 2001 From: Riccardo Pittau Date: Thu, 6 Mar 2025 12:08:08 +0100 Subject: [PATCH 3/6] Fix bare metal info order in unit tests Info on bare metal components have changed order Change-Id: Ibbd76a115175caea9db038d0b6601b83f9c0c3e8 (cherry picked from commit b969cc865ab7b59214fe3fa86115d88decfe2823) --- .../unit/osc/v1/test_baremetal_chassis.py | 4 +- .../unit/osc/v1/test_baremetal_conductor.py | 15 ++--- .../tests/unit/osc/v1/test_baremetal_node.py | 58 ++++++++++--------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py b/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py index 9af485326..6422ea0d1 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py @@ -50,14 +50,14 @@ def setUp(self): self.arglist = [] self.verifylist = [] self.collist = ( + 'uuid', 'description', 'extra', - 'uuid', ) self.datalist = ( + baremetal_fakes.baremetal_chassis_uuid, baremetal_fakes.baremetal_chassis_description, baremetal_fakes.baremetal_chassis_extra, - baremetal_fakes.baremetal_chassis_uuid, ) self.actual_kwargs = {} diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py b/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py index e9708a970..4c0083405 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_conductor.py @@ -212,17 +212,18 @@ def test_conductor_show(self): *args, fields=None ) - collist = ('alive', - 'conductor_group', - 'drivers', - 'hostname', - ) + collist = ( + 'hostname', + 'conductor_group', + 'alive', + 'drivers', + ) self.assertEqual(collist, columns) datalist = ( - baremetal_fakes.baremetal_alive, + baremetal_fakes.baremetal_hostname, baremetal_fakes.baremetal_conductor_group, + baremetal_fakes.baremetal_alive, baremetal_fakes.baremetal_drivers, - baremetal_fakes.baremetal_hostname, ) self.assertEqual(datalist, tuple(data)) diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index 319491023..a1fab2b1a 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -759,22 +759,23 @@ def setUp(self): self.cmd = baremetal_node.CreateBaremetalNode(self.app, None) self.arglist = ['--driver', 'fake_driver'] self.verifylist = [('driver', 'fake_driver')] - self.collist = ('chassis_uuid', - 'instance_uuid', - 'maintenance', - 'name', - 'power_state', - 'provision_state', - 'uuid' - ) + self.collist = ( + 'uuid', + 'name', + 'instance_uuid', + 'power_state', + 'provision_state', + 'maintenance', + 'chassis_uuid' + ) self.datalist = ( - baremetal_fakes.baremetal_chassis_uuid_empty, - baremetal_fakes.baremetal_instance_uuid, - baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_name, + baremetal_fakes.baremetal_instance_uuid, baremetal_fakes.baremetal_power_state, baremetal_fakes.baremetal_provision_state, - baremetal_fakes.baremetal_uuid, + baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_chassis_uuid_empty, ) self.actual_kwargs = { 'driver': 'fake_driver' @@ -3704,25 +3705,26 @@ def test_baremetal_show(self): *args, fields=None ) - collist = ('chassis_uuid', - 'instance_uuid', - 'maintenance', - 'name', - 'power_state', - 'provision_state', - 'uuid' - ) + collist = ( + 'uuid', + 'name', + 'instance_uuid', + 'power_state', + 'provision_state', + 'maintenance', + 'chassis_uuid', + ) self.assertEqual(collist, columns) self.assertNotIn('ports', columns) self.assertNotIn('states', columns) datalist = ( - baremetal_fakes.baremetal_chassis_uuid_empty, - baremetal_fakes.baremetal_instance_uuid, - baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_name, + baremetal_fakes.baremetal_instance_uuid, baremetal_fakes.baremetal_power_state, baremetal_fakes.baremetal_provision_state, - baremetal_fakes.baremetal_uuid + baremetal_fakes.baremetal_maintenance, + baremetal_fakes.baremetal_chassis_uuid_empty, ) self.assertEqual(datalist, tuple(data)) @@ -4828,10 +4830,10 @@ def test_baremetal_node_history_list(self): columns, data = self.cmd.take_action(parsed_args) self.baremetal_mock.node.get_history_event.assert_called_once_with( 'node_uuid', 'event_uuid') - expected_columns = ('conductor', 'created_at', 'event', 'event_type', - 'severity', 'user', 'uuid') - expected_data = ('lap-conductor', 'time', 'meow', 'purring', 'info', - '0191', 'abcdef1') + expected_columns = ('uuid', 'created_at', 'severity', 'event', + 'event_type', 'conductor', 'user') + expected_data = ('abcdef1', 'time', 'info', 'meow', 'purring', + 'lap-conductor', '0191') self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, tuple(data)) From adb3f5f77a86e379c84c22cfca55148ba93ec4e6 Mon Sep 17 00:00:00 2001 From: Jay Faulkner Date: Tue, 13 May 2025 09:36:34 -0700 Subject: [PATCH 4/6] Stop using deprecated format_* from osc_utils Added our own formatter which replaces the deprecated/removed one. It's contents are essentially format_dict from osc-lib 3.2.0 stuffed into a cliff column object. Change-Id: If28bda0a11f3e4a0d02a6a52040e8ca65611822f --- ironicclient/common/utils.py | 24 +++++++++++++++++++ ironicclient/osc/v1/baremetal_chassis.py | 2 +- ironicclient/osc/v1/baremetal_conductor.py | 3 ++- ironicclient/osc/v1/baremetal_node.py | 2 +- ironicclient/osc/v1/baremetal_port.py | 2 +- ironicclient/osc/v1/baremetal_portgroup.py | 2 +- .../osc/v1/baremetal_volume_connector.py | 2 +- .../osc/v1/baremetal_volume_target.py | 2 +- .../tests/unit/osc/v1/test_baremetal_port.py | 4 ++-- ironicclient/tests/unit/v1/test_node.py | 2 +- 10 files changed, 35 insertions(+), 10 deletions(-) diff --git a/ironicclient/common/utils.py b/ironicclient/common/utils.py index 498dfabe4..33227771b 100644 --- a/ironicclient/common/utils.py +++ b/ironicclient/common/utils.py @@ -25,6 +25,7 @@ import tempfile import time +from cliff import columns from oslo_utils import strutils import yaml @@ -462,3 +463,26 @@ def get_json_data(data): return json.loads(data) except ValueError: return None + + +def format_hash(data): + if data is None: + return None + + output = "" + for s in sorted(data): + key_str = s + if isinstance(data[s], dict): + # NOTE(dtroyer): Only append the separator chars here, quoting + # is completely handled in the terminal case. + output = output + format_hash(data[s], prefix=key_str) + ", " + elif data[s] is not None: + output = output + key_str + "='" + str(data[s]) + "', " + else: + output = output + key_str + "=, " + return output[:-2] + + +class HashColumn(columns.FormattableColumn): + def human_readable(self): + return format_hash(self._value) diff --git a/ironicclient/osc/v1/baremetal_chassis.py b/ironicclient/osc/v1/baremetal_chassis.py index a84fc52f4..58dc5bb61 100644 --- a/ironicclient/osc/v1/baremetal_chassis.py +++ b/ironicclient/osc/v1/baremetal_chassis.py @@ -190,7 +190,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class SetBaremetalChassis(command.Command): diff --git a/ironicclient/osc/v1/baremetal_conductor.py b/ironicclient/osc/v1/baremetal_conductor.py index 627fd6227..2dbb9c8c5 100755 --- a/ironicclient/osc/v1/baremetal_conductor.py +++ b/ironicclient/osc/v1/baremetal_conductor.py @@ -21,6 +21,7 @@ from osc_lib import utils as oscutils from ironicclient.common.i18n import _ +from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields @@ -106,7 +107,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class ShowBaremetalConductor(command.ShowOne): diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index b32d66436..02d59a833 100755 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -925,7 +925,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class MaintenanceSetBaremetalNode(command.Command): diff --git a/ironicclient/osc/v1/baremetal_port.py b/ironicclient/osc/v1/baremetal_port.py index d6fc424a1..b51634728 100644 --- a/ironicclient/osc/v1/baremetal_port.py +++ b/ironicclient/osc/v1/baremetal_port.py @@ -547,4 +547,4 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'extra': oscutils.format_dict},) for s in data)) + 'extra': utils.HashColumn},) for s in data)) diff --git a/ironicclient/osc/v1/baremetal_portgroup.py b/ironicclient/osc/v1/baremetal_portgroup.py index bd7f1a2be..a1dc40486 100644 --- a/ironicclient/osc/v1/baremetal_portgroup.py +++ b/ironicclient/osc/v1/baremetal_portgroup.py @@ -252,7 +252,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class DeleteBaremetalPortGroup(command.Command): diff --git a/ironicclient/osc/v1/baremetal_volume_connector.py b/ironicclient/osc/v1/baremetal_volume_connector.py index f8b270ad1..cb2e9cd4b 100644 --- a/ironicclient/osc/v1/baremetal_volume_connector.py +++ b/ironicclient/osc/v1/baremetal_volume_connector.py @@ -218,7 +218,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class DeleteBaremetalVolumeConnector(command.Command): diff --git a/ironicclient/osc/v1/baremetal_volume_target.py b/ironicclient/osc/v1/baremetal_volume_target.py index 7940e482c..614781aae 100644 --- a/ironicclient/osc/v1/baremetal_volume_target.py +++ b/ironicclient/osc/v1/baremetal_volume_target.py @@ -234,7 +234,7 @@ def take_action(self, parsed_args): return (labels, (oscutils.get_item_properties(s, columns, formatters={ - 'Properties': oscutils.format_dict},) for s in data)) + 'Properties': utils.HashColumn},) for s in data)) class DeleteBaremetalVolumeTarget(command.Command): diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_port.py b/ironicclient/tests/unit/osc/v1/test_baremetal_port.py index 01797f72a..ef507c34c 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_port.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_port.py @@ -18,8 +18,8 @@ from unittest import mock from osc_lib.tests import utils as osctestutils -from osc_lib import utils as oscutils +from ironicclient.common import utils from ironicclient import exc from ironicclient.osc.v1 import baremetal_port from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes @@ -813,7 +813,7 @@ def test_baremetal_port_list_long(self): baremetal_fakes.baremetal_port_uuid, baremetal_fakes.baremetal_port_address, '', - oscutils.format_dict(baremetal_fakes.baremetal_port_extra), + utils.HashColumn(baremetal_fakes.baremetal_port_extra), baremetal_fakes.baremetal_uuid, '', '', diff --git a/ironicclient/tests/unit/v1/test_node.py b/ironicclient/tests/unit/v1/test_node.py index 9b23623cc..6cb5bcc8f 100644 --- a/ironicclient/tests/unit/v1/test_node.py +++ b/ironicclient/tests/unit/v1/test_node.py @@ -2240,7 +2240,7 @@ def side_effect(node_manager, node_ident, *args, **kwargs): mock_get.side_effect = side_effect self.assertRaisesRegex(exc.StateTransitionTimeout, - r'Node\(s\) node2', + r'.*node2.*', self.mgr.wait_for_provision_state, ['node1', 'node2'], 'active', timeout=0.001) From 6e0c3ed116a04e068d0b0d1418efb1f028ddfd41 Mon Sep 17 00:00:00 2001 From: Johannes Kulik Date: Mon, 4 Aug 2025 11:12:14 +0200 Subject: [PATCH 5/6] Fix parallel initial version negotiation If two parallel greenthreads use the same, uninitialized client, there's a race-condition when both enter `negotiate_version()`: there is a request made to Ironic, which hands over execution to the other greenthread. The first greenthread returning from the request changes the state of the client and the second one reads the updated state and thus thinks it's in an error-handling call instead of the initial negotiation - and errors out. We fix this by adding a lock around the initial call to `negotiate_version()`. Change-Id: I9ec03d2bc34017c7670fd6903e5353a8c91e9f17 Closes-Bug: #2119323 Signed-off-by: Johannes Kulik (cherry picked from commit ad1291cc9cb4fc6560a1f82d73a496168a6770c6) --- ironicclient/common/http.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index a71a22a6f..f3d315bf9 100644 --- a/ironicclient/common/http.py +++ b/ironicclient/common/http.py @@ -19,6 +19,7 @@ import logging import re import textwrap +import threading import time from urllib import parse as urlparse @@ -345,6 +346,7 @@ def __init__(self, _('The Bare Metal API endpoint cannot be detected and was ' 'not provided explicitly')) self.endpoint_trimmed = _trim_endpoint_api_version(endpoint) + self._first_negotiation_lock = threading.Lock() def _parse_version_headers(self, resp): return self._generic_parse_version_headers(resp.headers.get) @@ -369,7 +371,9 @@ def _http_request(self, url, method, **kwargs): # NOTE(TheJulia): self.os_ironic_api_version is reset in # the self.negotiate_version() call if negotiation occurs. if self.os_ironic_api_version and self._must_negotiate_version(): - self.negotiate_version(self.session, None) + with self._first_negotiation_lock: + if self._must_negotiate_version(): + self.negotiate_version(self.session, None) kwargs.setdefault('user_agent', USER_AGENT) kwargs.setdefault('auth', self.auth) From 6ad50eedffc70e6038a51f98c03fd449666cb2f0 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Thu, 16 Apr 2026 16:30:47 -0500 Subject: [PATCH 6/6] [stable only] pin setuptools to fix pkg_resources errors pkg_resources has been removed in newer setuptools and we've not had the version pinned in OpenStack dependencies so it has broken stable branches. Change-Id: Ibb2f212d785de14200ed9beb99bd874b0fb0709b Signed-off-by: Doug Goldstein --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0bbf03398..a9d30dc5b 100644 --- a/tox.ini +++ b/tox.ini @@ -30,6 +30,7 @@ deps = flake8-import-order>=0.17.1 # LGPLv3 pycodestyle>=2.0.0,<3.0.0 # MIT Pygments>=2.2.0 # BSD + setuptools<82.0.0 -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} commands = flake8 {posargs} @@ -57,6 +58,7 @@ commands = {posargs} deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2025.1} -r{toxinidir}/doc/requirements.txt + setuptools<82.0.0 commands = sphinx-build -W -b html doc/source doc/build/html @@ -91,4 +93,4 @@ deps = codespell # note(JayF): {posargs} lets us run `tox -ecodespell -- -w` to get codespell # to correct spelling issues in our code it's aware of. commands = - codespell {posargs} \ No newline at end of file + codespell {posargs}