Add a new 'vendor' field to the Port object

Adds a new vendor field to the port object. This is foundational work
for trait based port scheduling.

Depends-On: https://review.opendev.org/c/openstack/ironic/+/957166
Change-Id: Ifce7da0a123e9f36a83f1a6a34759b25c9b2e416
Signed-off-by: Clif Houck <me@clifhouck.com>
This commit is contained in:
Clif Houck
2025-07-14 10:39:41 -05:00
parent ca65f2d3b7
commit 8a2e33e808
21 changed files with 160 additions and 7 deletions

View File

@@ -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:**

View File

@@ -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:**

View File

@@ -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

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -2,6 +2,11 @@
REST API Version History
========================
1.100 (Flamingo)
-----------------------
Add a 'vendor' field to the Port object.
1.99 (Flamingo)
-----------------------

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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'],

View File

@@ -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))

View File

@@ -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",

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'),
}

View File

@@ -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',

View File

@@ -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.