diff --git a/api-ref/source/baremetal-api-v1-portgroups-ports.inc b/api-ref/source/baremetal-api-v1-portgroups-ports.inc index 073bdfde13..5cd32ed417 100644 --- a/api-ref/source/baremetal-api-v1-portgroups-ports.inc +++ b/api-ref/source/baremetal-api-v1-portgroups-ports.inc @@ -28,6 +28,15 @@ Response to include only the specified fields, rather than the default set. .. versionadded:: 1.53 Added the ``is_smartnic`` response fields. +.. versionadded:: 1.88 + Added the ``name`` field. + +.. versionadded:: 1.97 + Added the ``description`` field. + +.. versionadded:: 1.100 + Added the ``vendor`` field. + Normal response code: 200 Error codes: 400,401,403,404 @@ -72,6 +81,15 @@ Return a detailed list of bare metal Ports associated with ``portgroup_ident``. .. versionadded:: 1.53 Added the ``is_smartnic`` response fields. +.. versionadded:: 1.88 + Added the ``name`` field. + +.. versionadded:: 1.97 + Added the ``description`` field. + +.. versionadded:: 1.100 + Added the ``vendor`` field. + Normal response code: 200 Error codes: 400,401,403,404 @@ -106,6 +124,9 @@ Response - updated_at: updated_at - links: links - is_smartnic: is_smartnic + - name: port_name + - description: port_description + - vendor: port_vendor **Example details of a Portgroup's Ports:** diff --git a/api-ref/source/baremetal-api-v1-ports.inc b/api-ref/source/baremetal-api-v1-ports.inc index c22865e734..15716016e8 100644 --- a/api-ref/source/baremetal-api-v1-ports.inc +++ b/api-ref/source/baremetal-api-v1-ports.inc @@ -56,6 +56,9 @@ By default, this query will return the uuid and address for each Port. .. versionadded:: 1.97 Added the ``description`` field. +.. versionadded:: 1.100 + Added the ``vendor`` field. + Normal response code: 200 Request @@ -128,6 +131,9 @@ This method requires a Node UUID and the physical hardware address for the Port .. versionadded:: 1.97 Added the ``description`` field. +.. versionadded:: 1.100 + Added the ``vendor`` field. + Normal response code: 201 Request @@ -146,6 +152,7 @@ Request - is_smartnic: req_is_smartnic - uuid: req_uuid - description: req_port_description + - vendor: req_port_vendor .. note:: Either `node_ident` or `node_uuid` is a valid parameter. @@ -175,6 +182,7 @@ Response - links: links - is_smartnic: is_smartnic - description: port_description + - vendor: port_vendor **Example Port creation response:** @@ -216,6 +224,9 @@ Return a list of bare metal Ports, with detailed information. .. versionadded:: 1.97 Added the ``description`` field. +.. versionadded:: 1.100 + Added the ``vendor`` field. + Normal response code: 200 Request @@ -254,6 +265,7 @@ Response - links: links - is_smartnic: is_smartnic - description: port_description + - vendor: port_vendor **Example detailed Port list response:** diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 53c2d87d13..02fabf9215 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -1582,6 +1582,12 @@ port_name: in: body required: false type: string +port_vendor: + description: | + Name of the hardware vendor of the network Port. + in: body + required: false + type: string portgroup_address: description: | Physical hardware address of this Portgroup, typically the hardware @@ -1983,6 +1989,12 @@ req_port_name: in: body required: false type: string +req_port_vendor: + description: | + Name of the hardware vendor of the network Port. + in: body + required: false + type: string req_portgroup_address: description: | Physical hardware address of this Portgroup, typically the hardware diff --git a/api-ref/source/samples/node-port-detail-response.json b/api-ref/source/samples/node-port-detail-response.json index 4b7a657999..0ac38ed7d3 100644 --- a/api-ref/source/samples/node-port-detail-response.json +++ b/api-ref/source/samples/node-port-detail-response.json @@ -23,6 +23,7 @@ }, "node_uuid": "6d85703a-565d-469a-96ce-30b6de53079d", "physical_network": "physnet1", + "vendor": "splitrock", "portgroup_uuid": "e43c722c-248e-4c6e-8ce8-0d8ff129387a", "pxe_enabled": true, "updated_at": "2016-08-18T22:28:49.653974+00:00", diff --git a/api-ref/source/samples/port-create-request.json b/api-ref/source/samples/port-create-request.json index cf3a4dd219..719a93482f 100644 --- a/api-ref/source/samples/port-create-request.json +++ b/api-ref/source/samples/port-create-request.json @@ -3,6 +3,7 @@ "portgroup_uuid": "e43c722c-248e-4c6e-8ce8-0d8ff129387a", "name": "port1", "description": "Physical Network", + "vendor": "splitrock", "address": "11:11:11:11:11:11", "is_smartnic": true, "local_link_connection": { diff --git a/api-ref/source/samples/port-create-response.json b/api-ref/source/samples/port-create-response.json index 9436650040..5d8b1c1028 100644 --- a/api-ref/source/samples/port-create-response.json +++ b/api-ref/source/samples/port-create-response.json @@ -21,6 +21,7 @@ }, "name": "port1", "description": "Physical Network", + "vendor": "splitrock", "node_uuid": "6d85703a-565d-469a-96ce-30b6de53079d", "physical_network": "physnet1", "portgroup_uuid": "e43c722c-248e-4c6e-8ce8-0d8ff129387a", diff --git a/api-ref/source/samples/port-list-detail-response.json b/api-ref/source/samples/port-list-detail-response.json index 405707b1c0..3c0572f314 100644 --- a/api-ref/source/samples/port-list-detail-response.json +++ b/api-ref/source/samples/port-list-detail-response.json @@ -23,6 +23,7 @@ }, "name": "port1", "description": "Physical Network", + "vendor": "splitrock", "node_uuid": "6d85703a-565d-469a-96ce-30b6de53079d", "physical_network": "physnet1", "portgroup_uuid": "e43c722c-248e-4c6e-8ce8-0d8ff129387a", diff --git a/api-ref/source/samples/port-update-response.json b/api-ref/source/samples/port-update-response.json index e2eb622a6a..8687835ed7 100644 --- a/api-ref/source/samples/port-update-response.json +++ b/api-ref/source/samples/port-update-response.json @@ -21,6 +21,7 @@ }, "name": "port1", "description": "Physical Network", + "vendor": "splitrock", "node_uuid": "6d85703a-565d-469a-96ce-30b6de53079d", "physical_network": "physnet1", "portgroup_uuid": "e43c722c-248e-4c6e-8ce8-0d8ff129387a", diff --git a/doc/source/contributor/webapi-version-history.rst b/doc/source/contributor/webapi-version-history.rst index 7707d70a54..ed75f818c3 100644 --- a/doc/source/contributor/webapi-version-history.rst +++ b/doc/source/contributor/webapi-version-history.rst @@ -2,6 +2,11 @@ REST API Version History ======================== +1.100 (Flamingo) +----------------------- + +Add a 'vendor' field to the Port object. + 1.99 (Flamingo) ----------------------- diff --git a/ironic/api/controllers/v1/port.py b/ironic/api/controllers/v1/port.py index 929d7e6a70..888c12ee7c 100644 --- a/ironic/api/controllers/v1/port.py +++ b/ironic/api/controllers/v1/port.py @@ -54,6 +54,7 @@ PORT_SCHEMA = { 'uuid': {'type': ['string', 'null']}, 'name': {'type': ['string', 'null']}, 'description': {'type': ['string', 'null'], 'maxLength': 255}, + 'vendor': {'type': ['string', 'null'], 'maxLength': 32}, }, 'required': ['address'], 'oneOf': [ @@ -78,6 +79,7 @@ PATCH_ALLOWED_FIELDS = [ 'pxe_enabled', 'name', 'description', + 'vendor', ] PORT_VALIDATOR_EXTRA = args.dict_valid( @@ -139,6 +141,9 @@ def hide_fields_in_newer_versions(port): # if requested version is < 1.97, hide description field. if not api_utils.allow_port_description(): port.pop('description', None) + # if requested version is < 1.100, hide vendor field. + if not api_utils.allow_port_vendor(): + port.pop('vendor', None) def convert_with_links(rpc_port, fields=None, sanitize=True): @@ -405,6 +410,9 @@ class PortsController(rest.RestController): if ('description' in fields and not api_utils.allow_port_description()): raise exception.NotAcceptable() + if ('vendor' in fields + and not api_utils.allow_port_vendor()): + raise exception.NotAcceptable() @METRICS.timer('PortsController.get_all') @method.expose() diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py index 2bb0b83404..15c0e755a6 100644 --- a/ironic/api/controllers/v1/utils.py +++ b/ironic/api/controllers/v1/utils.py @@ -2242,3 +2242,11 @@ def allow_port_description(): Version 1.97 of the API added description field to the port object. """ return api.request.version.minor >= versions.MINOR_97_PORT_DESCRIPTION + + +def allow_port_vendor(): + """Check if vendor is allowed for ports. + + Version 1.100 of the API added description field to the port object. + """ + return api.request.version.minor >= versions.MINOR_100_PORT_VENDOR diff --git a/ironic/api/controllers/v1/versions.py b/ironic/api/controllers/v1/versions.py index 44fd535eca..739e2af58a 100644 --- a/ironic/api/controllers/v1/versions.py +++ b/ironic/api/controllers/v1/versions.py @@ -137,6 +137,7 @@ BASE_VERSION = 1 # v1.97: Add description field to port. # v1.98: Add support for object attributes with keys containing ~ or /. # v1.99: Add conductor group filtering to port and portgroup list +# v1.100: Add vendor field to port. MINOR_0_JUNO = 0 MINOR_1_INITIAL_VERSION = 1 @@ -238,6 +239,7 @@ MINOR_96_INSPECTION_RULES = 96 MINOR_97_PORT_DESCRIPTION = 97 MINOR_98_SUPPORT_SPECIAL_CHAR_IN_ATTRIBUTES = 98 MINOR_99_PORT_PORTGROUP_CONDUCTOR_GROUP_FILTER = 99 +MINOR_100_PORT_VENDOR = 100 # When adding another version, update: # - MINOR_MAX_VERSION @@ -246,7 +248,7 @@ MINOR_99_PORT_PORTGROUP_CONDUCTOR_GROUP_FILTER = 99 # - common/release_mappings.py, RELEASE_MAPPING['master']['api'] -MINOR_MAX_VERSION = MINOR_99_PORT_PORTGROUP_CONDUCTOR_GROUP_FILTER +MINOR_MAX_VERSION = MINOR_100_PORT_VENDOR # String representations of the minor and maximum versions _MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index 4df82d9653..8aafe559f6 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -896,7 +896,7 @@ RELEASE_MAPPING = { # make it below. To release, we will preserve a version matching # the release as a separate block of text, like above. 'master': { - 'api': '1.99', + 'api': '1.100', 'rpc': '1.61', 'objects': { 'Allocation': ['1.1'], @@ -908,7 +908,7 @@ RELEASE_MAPPING = { 'Chassis': ['1.3'], 'Deployment': ['1.0'], 'DeployTemplate': ['1.1'], - 'Port': ['1.12'], + 'Port': ['1.13'], 'Portgroup': ['1.5'], 'Trait': ['1.0'], 'TraitList': ['1.0'], diff --git a/ironic/db/sqlalchemy/alembic/versions/e4827561979d_add_vendor_attribute_to_port_object.py b/ironic/db/sqlalchemy/alembic/versions/e4827561979d_add_vendor_attribute_to_port_object.py new file mode 100644 index 0000000000..fe8e319ea0 --- /dev/null +++ b/ironic/db/sqlalchemy/alembic/versions/e4827561979d_add_vendor_attribute_to_port_object.py @@ -0,0 +1,31 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Add vendor attribute to Port object + +Revision ID: e4827561979d +Revises: 1c14278d6e33 +Create Date: 2025-07-16 00:18:19.332889 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = 'e4827561979d' +down_revision = '1c14278d6e33' + + +def upgrade(): + op.add_column('ports', sa.Column('vendor', sa.String(length=32), + nullable=True)) diff --git a/ironic/db/sqlalchemy/models.py b/ironic/db/sqlalchemy/models.py index e5ecc8fc58..131cf21d11 100644 --- a/ironic/db/sqlalchemy/models.py +++ b/ironic/db/sqlalchemy/models.py @@ -270,6 +270,7 @@ class Port(Base): is_smartnic = Column(Boolean, nullable=True, default=False) name = Column(String(255), nullable=True) description = Column(String(255), nullable=True) + vendor = Column(String(32), nullable=True) _node_uuid = orm.relationship( "Node", diff --git a/ironic/objects/port.py b/ironic/objects/port.py index 5706121492..ecda6c5b0b 100644 --- a/ironic/objects/port.py +++ b/ironic/objects/port.py @@ -46,7 +46,8 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): # Version 1.10: Add name field # Version 1.11: Add node_uuid field # Version 1.12: Add description field - VERSION = '1.12' + # Version 1.13: Add vendor field + VERSION = '1.13' dbapi = dbapi.get_instance() @@ -67,6 +68,7 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): default=False), 'name': object_fields.StringField(nullable=True), 'description': object_fields.StringField(nullable=True), + 'vendor': object_fields.StringField(nullable=True), } def _convert_field_by_version(self, field_name, introduced_version, @@ -120,6 +122,12 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): Version 1.10: remove name field for unsupported versions if remove_unavailable_fields is True. + Version 1.12: remove description for unsupported versions if + remove_unavailable_fields is True. + + Version 1.13: remove vendor for unsupported versions if + remove_unavailable_fields is True. + :param target_version: the desired version of the object :param remove_unavailable_fields: True to remove fields that are unavailable in the target version; set this to True when @@ -151,6 +159,9 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat): # Convert the description field. self._convert_field_by_version('description', (1, 12), target_version, remove_unavailable_fields) + # Convert the vendor field. + self._convert_field_by_version('vendor', (1, 13), target_version, + remove_unavailable_fields) # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable # methods can be used in the future to replace current explicit RPC calls. @@ -526,7 +537,8 @@ class PortCRUDPayload(notification.NotificationPayloadBase): # Version 1.3: Add "is_smartnic" field # Version 1.4: Add "name" field # Version 1.5: Add "description" field - VERSION = '1.5' + # Version 1.6: Add "vendor" field + VERSION = '1.6' SCHEMA = { 'address': ('port', 'address'), @@ -540,6 +552,7 @@ class PortCRUDPayload(notification.NotificationPayloadBase): 'is_smartnic': ('port', 'is_smartnic'), 'name': ('port', 'name'), 'description': ('port', 'description'), + 'vendor': ('port', 'vendor'), } fields = { @@ -558,6 +571,7 @@ class PortCRUDPayload(notification.NotificationPayloadBase): default=False), 'name': object_fields.StringField(nullable=True), 'description': object_fields.StringField(nullable=True), + 'vendor': object_fields.StringField(nullable=True), } def __init__(self, port, node_uuid, portgroup_uuid): diff --git a/ironic/tests/unit/api/controllers/v1/test_port.py b/ironic/tests/unit/api/controllers/v1/test_port.py index 01bf80e69c..318e711fb1 100644 --- a/ironic/tests/unit/api/controllers/v1/test_port.py +++ b/ironic/tests/unit/api/controllers/v1/test_port.py @@ -2000,6 +2000,7 @@ class TestPost(test_api_base.BaseApiTest): pdict.pop('portgroup_uuid') pdict.pop('name') pdict.pop('description') + pdict.pop('vendor') headers = {api_base.Version.string: str(api_v1.min_version())} response = self.post_json('/ports', pdict, headers=headers) self.assertEqual('application/json', response.content_type) diff --git a/ironic/tests/unit/db/test_ports.py b/ironic/tests/unit/db/test_ports.py index dded9aa8ee..9d80c4df09 100644 --- a/ironic/tests/unit/db/test_ports.py +++ b/ironic/tests/unit/db/test_ports.py @@ -342,3 +342,29 @@ class DbPortTestCase(base.DbTestCase): retrieved_port1 = self.dbapi.get_port_by_uuid(port1.uuid) self.assertEqual(new_description, retrieved_port1.description) + + def test_create_port_with_vendor(self): + vendor = 'intel' + port1 = db_utils.create_test_port( + uuid=uuidutils.generate_uuid(), + node_id=self.node.id, + address='52:54:00:cf:2d:42', + vendor=vendor) + + port2 = db_utils.create_test_port( + uuid=uuidutils.generate_uuid(), + node_id=self.node.id, + address='52:54:00:cf:2d:45', + vendor=vendor) + + self.assertEqual(vendor, port1.vendor) + self.assertEqual(vendor, port2.vendor) + + new_vendor = 'mikrotik' + updated_port = self.dbapi.update_port( + port1.id, {'vendor': new_vendor}) + + self.assertEqual(new_vendor, updated_port.vendor) + + retrieved_port1 = self.dbapi.get_port_by_uuid(port1.uuid) + self.assertEqual(new_vendor, retrieved_port1.vendor) diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index 25b730bf35..a6fed6bde6 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -289,6 +289,7 @@ def get_test_port(**kw): 'is_smartnic': kw.get('is_smartnic', False), 'name': kw.get('name'), 'description': kw.get('description'), + 'vendor': kw.get('vendor'), } diff --git a/ironic/tests/unit/objects/test_objects.py b/ironic/tests/unit/objects/test_objects.py index 7fe706853a..5dd4c7b149 100644 --- a/ironic/tests/unit/objects/test_objects.py +++ b/ironic/tests/unit/objects/test_objects.py @@ -678,7 +678,7 @@ expected_object_fingerprints = { 'Node': '1.41-baff7b2b06243d97448b720030b2e612', 'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6', 'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905', - 'Port': '1.12-49ecc658f87df43ba1bb65543a5987e0', + 'Port': '1.13-f79db5cc15189d3e8b257db582b2329e', 'Portgroup': '1.5-df4dc15967f67114d51176a98a901a83', 'Conductor': '1.4-a9703208fdab5fab8f1cec420be1b4a7', 'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370', @@ -699,7 +699,7 @@ expected_object_fingerprints = { 'NodeCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', 'NodeCRUDPayload': '1.15-9168946f843edd5859464aaa40ad70e0', 'PortCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', - 'PortCRUDPayload': '1.5-174b559bcc8e472cbe4c0afac2b40e66', + 'PortCRUDPayload': '1.6-72323673fb8b869200d91bcd6886acae', 'NodeMaintenanceNotification': '1.0-59acc533c11d306f149846f922739c15', 'NodeConsoleNotification': '1.0-59acc533c11d306f149846f922739c15', 'PortgroupCRUDNotification': '1.0-59acc533c11d306f149846f922739c15', diff --git a/releasenotes/notes/port-vendor-ad30b76dc23efc38.yaml b/releasenotes/notes/port-vendor-ad30b76dc23efc38.yaml new file mode 100644 index 0000000000..8b124fb120 --- /dev/null +++ b/releasenotes/notes/port-vendor-ad30b76dc23efc38.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + A new "vendor" field has been added to the Port object. This field is meant + to help distinguish between different types of Ports. Relevant to trait + based port scheduling feature.