From e2ffecc8add40082ca20fc29a65501bd29a3d606 Mon Sep 17 00:00:00 2001 From: Afonne-CID Date: Tue, 15 Jul 2025 17:40:06 +0100 Subject: [PATCH] A new `instance_name` field to the node object Add CLI support for ``node.instance_name`` Depends-On: https://review.opendev.org/c/openstack/ironic/+/952790 Change-Id: I8a5d88c9ea9433c986968b9c9af86a4a4bcfe566 Signed-off-by: Afonne-CID --- ironicclient/common/http.py | 2 +- ironicclient/osc/v1/baremetal_node.py | 31 +++++++-- ironicclient/tests/unit/common/test_http.py | 2 +- .../tests/unit/osc/v1/test_baremetal_node.py | 68 +++++++++++++++++++ ironicclient/v1/node.py | 9 ++- ironicclient/v1/resource_fields.py | 2 + 6 files changed, 105 insertions(+), 9 deletions(-) diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index 580cfef6e..afae176ad 100644 --- a/ironicclient/common/http.py +++ b/ironicclient/common/http.py @@ -37,7 +37,7 @@ from ironicclient import exc # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # for full details. DEFAULT_VER = '1.9' -LAST_KNOWN_API_VERSION = 96 +LAST_KNOWN_API_VERSION = 104 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LOG = logging.getLogger(__name__) diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index 02d59a833..1518460aa 100644 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -477,6 +477,10 @@ class CreateBaremetalNode(command.ShowOne): '--name', metavar='', help=_("Unique name for the node.")) + parser.add_argument( + '--instance-name', + metavar='', + help=_('Name of the instance deployed on this node.')) parser.add_argument( '--bios-interface', metavar='', @@ -611,8 +615,9 @@ class CreateBaremetalNode(command.ShowOne): field_list = ['automated_clean', 'chassis_uuid', 'disable_power_off', 'driver', 'driver_info', 'properties', 'extra', 'uuid', - 'name', 'conductor_group', 'owner', 'description', - 'lessee', 'shard', 'resource_class', 'parent_node', + 'name', 'instance_name', 'conductor_group', 'owner', + 'description', 'lessee', 'shard', 'resource_class', + 'parent_node', ] + ['%s_interface' % iface for iface in SUPPORTED_INTERFACES] fields = dict((k, v) for (k, v) in vars(parsed_args).items() @@ -827,6 +832,10 @@ class ListBaremetalNode(command.Lister): metavar='', help=_("Limit list to nodes with description contains " "")) + parser.add_argument( + '--instance-name', + metavar='', + help=_("Filter the list of returned nodes by an instance name.")) sharded_group = parser.add_mutually_exclusive_group(required=False) sharded_group.add_argument( '--sharded', @@ -901,7 +910,8 @@ class ListBaremetalNode(command.Lister): params[field] = getattr(parsed_args, field) for field in ['provision_state', 'driver', 'resource_class', 'chassis', 'conductor', 'owner', 'lessee', - 'description_contains', 'shards', 'parent_node']: + 'description_contains', 'shards', 'parent_node', + 'instance_name']: if getattr(parsed_args, field): params[field] = getattr(parsed_args, field) if parsed_args.include_children: @@ -1329,6 +1339,11 @@ class SetBaremetalNode(command.Command): metavar="", help=_("Set instance UUID of node to "), ) + parser.add_argument( + "--instance-name", + metavar="", + help=_('Set the name of the instance deployed on this node.'), + ) parser.add_argument( "--name", metavar="", @@ -1574,7 +1589,7 @@ class SetBaremetalNode(command.Command): baremetal_client.node.set_target_raid_config(node, raid_config) properties = [] - for field in ['instance_uuid', 'name', + for field in ['instance_uuid', 'instance_name', 'name', 'chassis_uuid', 'driver', 'resource_class', 'conductor_group', 'protected', 'protected_reason', 'retired', 'retired_reason', 'owner', 'lessee', @@ -1723,6 +1738,12 @@ class UnsetBaremetalNode(command.Command): default=False, help=_('Unset instance UUID on this baremetal node') ) + parser.add_argument( + '--instance-name', + action='store_true', + default=False, + help=_('Unset instance name on this baremetal node') + ) parser.add_argument( "--name", action='store_true', @@ -1932,7 +1953,7 @@ class UnsetBaremetalNode(command.Command): baremetal_client.node.set_target_raid_config(node, {}) properties = [] - for field in ['instance_uuid', 'name', 'chassis_uuid', + for field in ['instance_uuid', 'instance_name', 'name', 'chassis_uuid', 'resource_class', 'conductor_group', 'automated_clean', 'bios_interface', 'boot_interface', 'console_interface', 'deploy_interface', 'firmware_interface', diff --git a/ironicclient/tests/unit/common/test_http.py b/ironicclient/tests/unit/common/test_http.py index b892f0356..76f256008 100644 --- a/ironicclient/tests/unit/common/test_http.py +++ b/ironicclient/tests/unit/common/test_http.py @@ -205,7 +205,7 @@ class VersionNegotiationMixinTest(utils.BaseTestCase): def test_negotiate_version_server_user_latest( self, mock_pvh, mock_msr, mock_save_data): # have to retry with simple get - mock_pvh.side_effect = iter([(None, None), ('1.1', '1.99')]) + mock_pvh.side_effect = iter([(None, None), ('1.1', '1.104')]) mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'user' self.test_object.os_ironic_api_version = 'latest' diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index a1fab2b1a..f8eb87296 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -990,6 +990,11 @@ class TestBaremetalCreate(TestBaremetal): [('parent_node', 'nodex')], {'parent_node': 'nodex'}) + def test_baremetal_create_with_instance_name(self): + self.check_with_options(['--instance-name', 'for-instance'], + [('instance_name', 'for-instance')], + {'instance_name': 'for-instance'}) + def test_baremetal_create_with_disable_power_off(self): self.check_with_options(['--disable-power-off'], [('disable_power_off', True)], @@ -1167,6 +1172,7 @@ class TestBaremetalList(TestBaremetal): 'Inspection Finished At', 'Inspection Started At', 'Instance Info', + 'Instance Name', 'Instance UUID', 'Last Error', 'Lessee', @@ -1635,6 +1641,29 @@ class TestBaremetalList(TestBaremetal): **kwargs ) + def test_baremetal_list_by_instance_name(self): + instance_name = 'for-instance' + arglist = [ + '--instance-name', instance_name, + ] + verifylist = [ + ('instance_name', instance_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'marker': None, + 'limit': None, + 'instance_name': instance_name, + } + + self.baremetal_mock.node.list.assert_called_with( + **kwargs + ) + def test_baremetal_list_fields(self): arglist = [ '--fields', 'uuid', 'name', @@ -2991,6 +3020,26 @@ class TestBaremetalSet(TestBaremetal): reset_interfaces=None, ) + def test_baremetal_set_instance_name(self): + arglist = [ + 'node_uuid', + '--instance-name', 'for-instance', + ] + verifylist = [ + ('nodes', ['node_uuid']), + ('instance_name', 'for-instance') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.update.assert_called_once_with( + 'node_uuid', + [{'path': '/instance_name', 'value': 'for-instance', 'op': 'add'}], + reset_interfaces=None, + ) + def test_baremetal_set_chassis(self): chassis = '4f4135ea-7e58-4e3d-bcc4-b87ca16e980b' arglist = [ @@ -3920,6 +3969,25 @@ class TestBaremetalUnset(TestBaremetal): [{'path': '/name', 'op': 'remove'}] ) + def test_baremetal_unset_instance_name(self): + arglist = [ + 'node_uuid', + '--instance-name', + ] + verifylist = [ + ('nodes', ['node_uuid']), + ('instance_name', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.update.assert_called_once_with( + 'node_uuid', + [{'path': '/instance_name', 'op': 'remove'}] + ) + def test_baremetal_unset_resource_class(self): arglist = [ 'node_uuid', diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py index c36a27d64..0c0974901 100644 --- a/ironicclient/v1/node.py +++ b/ironicclient/v1/node.py @@ -55,7 +55,8 @@ class NodeManager(base.CreateManager): 'vendor_interface', 'firmware_interface', 'resource_class', 'conductor_group', 'automated_clean', 'network_data', 'parent_node', - 'owner', 'lessee', 'shard', 'description'] + 'owner', 'lessee', 'shard', 'description', + 'instance_name'] _resource_name = 'nodes' def list(self, associated=None, maintenance=None, marker=None, @@ -66,7 +67,7 @@ class NodeManager(base.CreateManager): conductor=None, owner=None, retired=None, lessee=None, shards=None, sharded=None, parent_node=None, include_children=None, description_contains=None, - global_request_id=None): + global_request_id=None, instance_name=None): """Retrieve a list of nodes. :param associated: Optional. Either a Boolean or a string @@ -147,6 +148,8 @@ class NodeManager(base.CreateManager): node list. :param description_contains: Optional. String value to get nodes with description contains specified value. + :param instance_name: Optional. String value to get only nodes with + the given instance_name set. :returns: A list of nodes. """ @@ -194,6 +197,8 @@ class NodeManager(base.CreateManager): filters.append('include_children=True') if description_contains is not None: filters.append('description_contains=%s' % description_contains) + if instance_name is not None: + filters.append('instance_name=%s' % instance_name) path = '' if detail: diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py index 4854db3ac..130afdda2 100644 --- a/ironicclient/v1/resource_fields.py +++ b/ironicclient/v1/resource_fields.py @@ -90,6 +90,7 @@ class Resource(object): 'inspection_finished_at': 'Inspection Finished At', 'inspection_started_at': 'Inspection Started At', 'instance_info': 'Instance Info', + 'instance_name': 'Instance Name', 'instance_uuid': 'Instance UUID', 'internal_info': 'Internal Info', 'last_error': 'Last Error', @@ -273,6 +274,7 @@ NODE_DETAILED_RESOURCE = Resource( 'inspection_finished_at', 'inspection_started_at', 'instance_info', + 'instance_name', 'instance_uuid', 'last_error', 'lessee',