From b2c1dfac697b3270032f0417ff1b38ee516aeda5 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Tue, 13 Jan 2026 12:24:32 -0600 Subject: [PATCH] feat: add 'vendor' and 'category' for port object Added support for the 'vendor' and 'category' fields for the port object. Change-Id: Id2ec4308b1ab4c9fba538c811af52b32206730f8 Signed-off-by: Doug Goldstein (cherry picked from commit 924107bcd2b1b0676ac81392a2feb3644e37827d) --- ironicclient/osc/v1/baremetal_port.py | 53 +++++++- .../tests/unit/osc/v1/test_baremetal_port.py | 113 ++++++++++++++++++ ironicclient/v1/port.py | 2 +- ironicclient/v1/resource_fields.py | 4 + ...port-vendor-category-4f046b55eec698bf.yaml | 6 + 5 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-port-vendor-category-4f046b55eec698bf.yaml diff --git a/ironicclient/osc/v1/baremetal_port.py b/ironicclient/osc/v1/baremetal_port.py index 632b4befc..cd7b315e8 100644 --- a/ironicclient/osc/v1/baremetal_port.py +++ b/ironicclient/osc/v1/baremetal_port.py @@ -118,6 +118,18 @@ class CreateBaremetalPort(command.ShowOne): metavar='', help=_("An optional description for the port.")) + parser.add_argument( + '--vendor', + dest='vendor', + metavar='', + help=_("An optional vendor for the port.")) + + parser.add_argument( + '--category', + dest='category', + metavar='', + help=_("An optional category for the port.")) + return parser def take_action(self, parsed_args): @@ -138,7 +150,8 @@ class CreateBaremetalPort(command.ShowOne): field_list = ['address', 'uuid', 'extra', 'node_uuid', 'pxe_enabled', 'local_link_connection', 'portgroup_uuid', - 'physical_network', 'name', 'description'] + 'physical_network', 'name', 'description', 'vendor', + 'category'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) fields = utils.args_array_to_dict(fields, 'extra') @@ -255,6 +268,18 @@ class UnsetBaremetalPort(command.Command): action='store_true', help=_("Unset the description for this port.")) + parser.add_argument( + '--vendor', + dest='vendor', + action='store_true', + help=_("Unset the vendor for this port.")) + + parser.add_argument( + '--category', + dest='category', + action='store_true', + help=_("Unset the category for this port.")) + return parser def take_action(self, parsed_args): @@ -281,6 +306,12 @@ class UnsetBaremetalPort(command.Command): if parsed_args.description: properties.extend(utils.args_array_to_patch('remove', ['description'])) + if parsed_args.vendor: + properties.extend(utils.args_array_to_patch('remove', + ['vendor'])) + if parsed_args.category: + properties.extend(utils.args_array_to_patch('remove', + ['category'])) if properties: baremetal_client.port.update(parsed_args.port, properties) @@ -378,6 +409,18 @@ class SetBaremetalPort(command.Command): dest='description', help=_("Set a description for this port")) + parser.add_argument( + '--vendor', + metavar='', + dest='vendor', + help=_("Set a vendor for this port")) + + parser.add_argument( + '--category', + metavar='', + dest='category', + help=_("Set a category for this port")) + return parser def take_action(self, parsed_args): @@ -421,6 +464,14 @@ class SetBaremetalPort(command.Command): port_description = ["description=%s" % parsed_args.description] properties.extend(utils.args_array_to_patch('add', port_description)) + if parsed_args.vendor: + port_vendor = ["vendor=%s" % parsed_args.vendor] + properties.extend(utils.args_array_to_patch('add', + port_vendor)) + if parsed_args.category: + port_category = ["category=%s" % parsed_args.category] + properties.extend(utils.args_array_to_patch('add', + port_category)) if properties: baremetal_client.port.update(parsed_args.port, properties) diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_port.py b/ironicclient/tests/unit/osc/v1/test_baremetal_port.py index fa05df128..467bf78cc 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_port.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_port.py @@ -326,6 +326,60 @@ class TestCreateBaremetalPort(TestBaremetalPort): self.baremetal_mock.port.create.assert_called_once_with(**args) + def test_baremetal_port_create_vendor(self): + arglist = [ + baremetal_fakes.baremetal_port_address, + '--node', baremetal_fakes.baremetal_uuid, + '--vendor', 'VendorA' + ] + + verifylist = [ + ('node_uuid', baremetal_fakes.baremetal_uuid), + ('address', baremetal_fakes.baremetal_port_address), + ('vendor', 'VendorA') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + # Set expected values + args = { + 'address': baremetal_fakes.baremetal_port_address, + 'node_uuid': baremetal_fakes.baremetal_uuid, + 'vendor': 'VendorA' + } + + self.baremetal_mock.port.create.assert_called_once_with(**args) + + def test_baremetal_port_create_category(self): + arglist = [ + baremetal_fakes.baremetal_port_address, + '--node', baremetal_fakes.baremetal_uuid, + '--category', 'Green' + ] + + verifylist = [ + ('node_uuid', baremetal_fakes.baremetal_uuid), + ('address', baremetal_fakes.baremetal_port_address), + ('category', 'Green') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + # Set expected values + args = { + 'address': baremetal_fakes.baremetal_port_address, + 'node_uuid': baremetal_fakes.baremetal_uuid, + 'category': 'Green' + } + + self.baremetal_mock.port.create.assert_called_once_with(**args) + class TestShowBaremetalPort(TestBaremetalPort): def setUp(self): @@ -505,6 +559,30 @@ class TestBaremetalPortUnset(TestBaremetalPort): 'port', [{'path': '/description', 'op': 'remove'}]) + def test_baremetal_port_unset_vendor(self): + arglist = ['port', '--vendor'] + verifylist = [('port', 'port'), + ('vendor', True)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.baremetal_mock.port.update.assert_called_once_with( + 'port', + [{'path': '/vendor', 'op': 'remove'}]) + + def test_baremetal_port_unset_category(self): + arglist = ['port', '--category'] + verifylist = [('port', 'port'), + ('category', True)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.baremetal_mock.port.update.assert_called_once_with( + 'port', + [{'path': '/category', 'op': 'remove'}]) + class TestBaremetalPortSet(TestBaremetalPort): def setUp(self): @@ -705,6 +783,38 @@ class TestBaremetalPortSet(TestBaremetalPort): [{'path': '/description', 'value': 'Public Network', 'op': 'add'}]) + def test_baremetal_port_set_vendor(self): + arglist = [ + baremetal_fakes.baremetal_port_uuid, + '--vendor', 'VendorA'] + verifylist = [ + ('port', baremetal_fakes.baremetal_port_uuid), + ('vendor', 'VendorA')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.baremetal_mock.port.update.assert_called_once_with( + baremetal_fakes.baremetal_port_uuid, + [{'path': '/vendor', 'value': 'VendorA', + 'op': 'add'}]) + + def test_baremetal_port_set_category(self): + arglist = [ + baremetal_fakes.baremetal_port_uuid, + '--category', 'Green'] + verifylist = [ + ('port', baremetal_fakes.baremetal_port_uuid), + ('category', 'Green')] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.baremetal_mock.port.update.assert_called_once_with( + baremetal_fakes.baremetal_port_uuid, + [{'path': '/category', 'value': 'Green', + 'op': 'add'}]) + class TestBaremetalPortDelete(TestBaremetalPort): def setUp(self): @@ -858,6 +968,7 @@ class TestBaremetalPortList(TestBaremetalPort): self.baremetal_mock.port.list.assert_called_with(**kwargs) collist = ('UUID', 'Address', 'Created At', 'Extra', 'Node UUID', + 'Category', 'Vendor', 'Local Link Connection', 'Portgroup UUID', 'PXE boot enabled', 'Physical Network', 'Updated At', 'Internal Info', 'Is Smart NIC port', @@ -878,6 +989,8 @@ class TestBaremetalPortList(TestBaremetalPort): '', '', '', + '', + '', ), ) self.assertEqual(datalist, tuple(data)) diff --git a/ironicclient/v1/port.py b/ironicclient/v1/port.py index a2b41e898..95f51df8e 100644 --- a/ironicclient/v1/port.py +++ b/ironicclient/v1/port.py @@ -30,7 +30,7 @@ class PortManager(base.CreateManager): _creation_attributes = ['address', 'extra', 'local_link_connection', 'node_uuid', 'physical_network', 'portgroup_uuid', 'pxe_enabled', 'uuid', 'is_smartnic', 'name', - 'description'] + 'description', 'vendor', 'category'] _resource_name = 'ports' def list(self, address=None, limit=None, marker=None, sort_key=None, diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py index 4854db3ac..348638a17 100644 --- a/ironicclient/v1/resource_fields.py +++ b/ironicclient/v1/resource_fields.py @@ -131,6 +131,8 @@ class Resource(object): 'value': 'Value', 'volume_id': 'Volume ID', 'volume_type': 'Driver Volume Type', + 'category': 'Category', + 'vendor': 'Vendor', 'local_link_connection': 'Local Link Connection', 'pxe_enabled': 'PXE boot enabled', 'portgroup_uuid': 'Portgroup UUID', @@ -347,6 +349,8 @@ PORT_DETAILED_RESOURCE = Resource( 'created_at', 'extra', 'node_uuid', + 'category', + 'vendor', 'local_link_connection', 'portgroup_uuid', 'pxe_enabled', diff --git a/releasenotes/notes/add-port-vendor-category-4f046b55eec698bf.yaml b/releasenotes/notes/add-port-vendor-category-4f046b55eec698bf.yaml new file mode 100644 index 000000000..e270b5a5e --- /dev/null +++ b/releasenotes/notes/add-port-vendor-category-4f046b55eec698bf.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds ``vendor`` field and ``category`` field support, which is introduced + in ironic API 1.100 and 1.101 respectively. These field is used to store + informational text about the port for trait based scheduling.