Merge "Use association_proxy for ports node_uuid"

This commit is contained in:
Zuul 2023-01-10 16:08:30 +00:00 committed by Gerrit Code Review
commit 81e10265ce
9 changed files with 33 additions and 44 deletions

View File

@ -123,9 +123,9 @@ def convert_with_links(rpc_port, fields=None, sanitize=True):
'local_link_connection', 'local_link_connection',
'physical_network', 'physical_network',
'pxe_enabled', 'pxe_enabled',
'node_uuid',
) )
) )
api_utils.populate_node_uuid(rpc_port, port)
if rpc_port.portgroup_id: if rpc_port.portgroup_id:
pg = objects.Portgroup.get(api.request.context, rpc_port.portgroup_id) pg = objects.Portgroup.get(api.request.context, rpc_port.portgroup_id)
port['portgroup_uuid'] = pg.uuid port['portgroup_uuid'] = pg.uuid
@ -166,11 +166,9 @@ def list_convert_with_links(rpc_ports, limit, url, fields=None, **kwargs):
try: try:
port = convert_with_links(rpc_port, fields=fields, port = convert_with_links(rpc_port, fields=fields,
sanitize=False) sanitize=False)
except exception.NodeNotFound:
# NOTE(dtantsur): node was deleted after we fetched the port # NOTE(dtantsur): node was deleted after we fetched the port
# list, meaning that the port was also deleted. Skip it. # list, meaning that the port was also deleted. Skip it.
LOG.debug('Skipping port %s as its node was deleted', if port['node_uuid'] is None:
rpc_port.uuid)
continue continue
except exception.PortgroupNotFound: except exception.PortgroupNotFound:
# NOTE(dtantsur): port group was deleted after we fetched the # NOTE(dtantsur): port group was deleted after we fetched the

View File

@ -523,7 +523,7 @@ RELEASE_MAPPING = {
'Chassis': ['1.3'], 'Chassis': ['1.3'],
'Deployment': ['1.0'], 'Deployment': ['1.0'],
'DeployTemplate': ['1.1'], 'DeployTemplate': ['1.1'],
'Port': ['1.10'], 'Port': ['1.11'],
'Portgroup': ['1.4'], 'Portgroup': ['1.4'],
'Trait': ['1.0'], 'Trait': ['1.0'],
'TraitList': ['1.0'], 'TraitList': ['1.0'],

View File

@ -980,9 +980,7 @@ class Connection(api.Connection):
sort_key=None, sort_dir=None, owner=None, sort_key=None, sort_dir=None, owner=None,
project=None): project=None):
query = sa.select(models.Port).where( query = sa.select(models.Port).where(
models.Port.portgroup_id == portgroup_id models.Port.portgroup_id == portgroup_id)
)
if owner: if owner:
query = add_port_filter_by_node_owner(query, owner) query = add_port_filter_by_node_owner(query, owner)
elif project: elif project:

View File

@ -25,6 +25,7 @@ from urllib import parse as urlparse
from oslo_db import options as db_options from oslo_db import options as db_options
from oslo_db.sqlalchemy import models from oslo_db.sqlalchemy import models
from oslo_db.sqlalchemy import types as db_types from oslo_db.sqlalchemy import types as db_types
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import Boolean, Column, DateTime, false, Index from sqlalchemy import Boolean, Column, DateTime, false, Index
from sqlalchemy import ForeignKey, Integer from sqlalchemy import ForeignKey, Integer
from sqlalchemy import schema, String, Text from sqlalchemy import schema, String, Text
@ -262,6 +263,15 @@ class Port(Base):
is_smartnic = Column(Boolean, nullable=True, default=False) is_smartnic = Column(Boolean, nullable=True, default=False)
name = Column(String(255), nullable=True) name = Column(String(255), nullable=True)
_node_uuid = orm.relationship(
"Node",
viewonly=True,
primaryjoin="(Node.id == Port.node_id)",
lazy="selectin",
)
node_uuid = association_proxy(
"_node_uuid", "uuid", creator=lambda _i: Node(uuid=_i))
class Portgroup(Base): class Portgroup(Base):
"""Represents a group of network ports of a bare metal node.""" """Represents a group of network ports of a bare metal node."""

View File

@ -44,7 +44,8 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
# change) # change)
# Version 1.9: Add support for Smart NIC port # Version 1.9: Add support for Smart NIC port
# Version 1.10: Add name field # Version 1.10: Add name field
VERSION = '1.10' # Version 1.11: Add node_uuid field
VERSION = '1.11'
dbapi = dbapi.get_instance() dbapi = dbapi.get_instance()
@ -52,6 +53,7 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
'id': object_fields.IntegerField(), 'id': object_fields.IntegerField(),
'uuid': object_fields.UUIDField(nullable=True), 'uuid': object_fields.UUIDField(nullable=True),
'node_id': object_fields.IntegerField(nullable=True), 'node_id': object_fields.IntegerField(nullable=True),
'node_uuid': object_fields.UUIDField(nullable=True),
'address': object_fields.MACAddressField(nullable=True), 'address': object_fields.MACAddressField(nullable=True),
'extra': object_fields.FlexibleDictField(nullable=True), 'extra': object_fields.FlexibleDictField(nullable=True),
'local_link_connection': object_fields.FlexibleDictField( 'local_link_connection': object_fields.FlexibleDictField(
@ -377,6 +379,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
""" """
values = self.do_version_changes_for_db() values = self.do_version_changes_for_db()
db_port = self.dbapi.create_port(values) db_port = self.dbapi.create_port(values)
# NOTE(hjensas): To avoid lazy load issue (DetachedInstanceError) in
# sqlalchemy, get new port the port from the DB to ensure the node_uuid
# via association_proxy relationship is loaded.
db_port = self.dbapi.get_port_by_id(db_port['id'])
self._from_db_object(self._context, self, db_port) self._from_db_object(self._context, self, db_port)
# NOTE(xek): We don't want to enable RPC on this call just yet. Remotable # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable

View File

@ -236,35 +236,6 @@ class TestListPorts(test_api_base.BaseApiTest):
# never expose the node_id # never expose the node_id
self.assertNotIn('node_id', data['ports'][0]) self.assertNotIn('node_id', data['ports'][0])
# NOTE(jlvillal): autospec=True doesn't work on staticmethods:
# https://bugs.python.org/issue23078
@mock.patch.object(objects.Node, 'get_by_id', spec_set=types.FunctionType)
def test_list_with_deleted_node(self, mock_get_node):
# check that we don't end up with HTTP 400 when node deletion races
# with listing ports - see https://launchpad.net/bugs/1748893
obj_utils.create_test_port(self.context, node_id=self.node.id)
mock_get_node.side_effect = exception.NodeNotFound('boom')
data = self.get_json('/ports')
self.assertEqual([], data['ports'])
# NOTE(jlvillal): autospec=True doesn't work on staticmethods:
# https://bugs.python.org/issue23078
@mock.patch.object(objects.Node, 'get_by_id',
spec_set=types.FunctionType)
def test_list_detailed_with_deleted_node(self, mock_get_node):
# check that we don't end up with HTTP 400 when node deletion races
# with listing ports - see https://launchpad.net/bugs/1748893
port = obj_utils.create_test_port(self.context, node_id=self.node.id)
port2 = obj_utils.create_test_port(self.context, node_id=self.node.id,
uuid=uuidutils.generate_uuid(),
address='66:44:55:33:11:22')
mock_get_node.side_effect = [exception.NodeNotFound('boom'), self.node]
data = self.get_json('/ports/detail')
# The "correct" port is still returned
self.assertEqual(1, len(data['ports']))
self.assertIn(data['ports'][0]['uuid'], {port.uuid, port2.uuid})
self.assertEqual(self.node.uuid, data['ports'][0]['node_uuid'])
# NOTE(jlvillal): autospec=True doesn't work on staticmethods: # NOTE(jlvillal): autospec=True doesn't work on staticmethods:
# https://bugs.python.org/issue23078 # https://bugs.python.org/issue23078
@mock.patch.object(objects.Portgroup, 'get', spec_set=types.FunctionType) @mock.patch.object(objects.Portgroup, 'get', spec_set=types.FunctionType)

View File

@ -272,6 +272,8 @@ def get_test_port(**kw):
'version': kw.get('version', port.Port.VERSION), 'version': kw.get('version', port.Port.VERSION),
'uuid': kw.get('uuid', '1be26c0b-03f2-4d2e-ae87-c02d7f33c781'), 'uuid': kw.get('uuid', '1be26c0b-03f2-4d2e-ae87-c02d7f33c781'),
'node_id': kw.get('node_id', 123), 'node_id': kw.get('node_id', 123),
'node_uuid': kw.get('node_uuid',
'59d102f7-5840-4299-8ec8-80c0ebae9de1'),
'address': kw.get('address', '52:54:00:cf:2d:31'), 'address': kw.get('address', '52:54:00:cf:2d:31'),
'extra': kw.get('extra', {}), 'extra': kw.get('extra', {}),
'created_at': kw.get('created_at'), 'created_at': kw.get('created_at'),

View File

@ -679,7 +679,7 @@ expected_object_fingerprints = {
'Node': '1.36-8a080e31ba89ca5f09e859bd259b54dc', 'Node': '1.36-8a080e31ba89ca5f09e859bd259b54dc',
'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6', 'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6',
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905', 'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
'Port': '1.10-67381b065c597c8d3a13c5dbc6243c33', 'Port': '1.11-97bf15b61224f26c65e90f007d78bfd2',
'Portgroup': '1.4-71923a81a86743b313b190f5c675e258', 'Portgroup': '1.4-71923a81a86743b313b190f5c675e258',
'Conductor': '1.3-d3f53e853b4d58cae5bfbd9a8341af4a', 'Conductor': '1.3-d3f53e853b4d58cae5bfbd9a8341af4a',
'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370', 'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370',

View File

@ -88,7 +88,11 @@ class TestPortObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
port = objects.Port(self.context, **self.fake_port) port = objects.Port(self.context, **self.fake_port)
with mock.patch.object(self.dbapi, 'create_port', with mock.patch.object(self.dbapi, 'create_port',
autospec=True) as mock_create_port: autospec=True) as mock_create_port:
mock_create_port.return_value = db_utils.get_test_port() with mock.patch.object(self.dbapi, 'get_port_by_id',
autospec=True) as mock_get_port:
test_port = db_utils.get_test_port()
mock_create_port.return_value = test_port
mock_get_port.return_value = test_port
port.create() port.create()