Add dbapi and objects functions to get a node by associated MAC addresses

Adds a new dbapi call get_node_by_port_addresses and associated objects call
Node.get_by_port_addresses. The logic is the same as in "lookup" agent passthru.

Will be used for a new lookup endpoint.

Change-Id: Ia5549fb16cd363f3492b9ca0400177c92a1aea19
Partial-Bug: #1570841
This commit is contained in:
Dmitry Tantsur 2016-06-17 09:42:33 +02:00 committed by Dmitry Tantsur
parent 90e3bcaeb7
commit 088f09903b
6 changed files with 106 additions and 3 deletions

View File

@ -604,3 +604,12 @@ class Connection(object):
:param tag: A tag string.
:returns: True if the tag exists otherwise False.
"""
@abc.abstractmethod
def get_node_by_port_addresses(self, addresses):
"""Find a node by any matching port address.
:param addresses: list of port addresses (e.g. MACs).
:returns: Node object.
:raises: NodeNotFound if none or several nodes are found.
"""

View File

@ -28,7 +28,7 @@ from oslo_log import log
from oslo_utils import strutils
from oslo_utils import timeutils
from oslo_utils import uuidutils
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from sqlalchemy.orm import joinedload
from sqlalchemy import sql
@ -843,3 +843,18 @@ class Connection(api.Connection):
def node_tag_exists(self, node_id, tag):
q = model_query(models.NodeTag).filter_by(node_id=node_id, tag=tag)
return model_query(q.exists()).scalar()
def get_node_by_port_addresses(self, addresses):
q = model_query(models.Node).distinct().join(models.Port)
q = q.filter(models.Port.address.in_(addresses))
try:
return q.one()
except NoResultFound:
raise exception.NodeNotFound(
_('Node with port addresses %s was not found')
% addresses)
except MultipleResultsFound:
raise exception.NodeNotFound(
_('Multiple nodes with port addresses %s were found')
% addresses)

View File

@ -45,7 +45,8 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
# Version 1.13: Add touch_provisioning()
# Version 1.14: Add _validate_property_values() and make create()
# and save() validate the input of property values.
VERSION = '1.14'
# Version 1.15: Add get_by_port_addresses
VERSION = '1.15'
dbapi = db_api.get_instance()
@ -364,3 +365,16 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
def touch_provisioning(self, context=None):
"""Touch the database record to mark the provisioning as alive."""
self.dbapi.touch_node_provisioning(self.id)
@classmethod
def get_by_port_addresses(cls, context, addresses):
"""Get a node by associated port addresses.
:param context: Security context.
:param addresses: A list of port addresses.
:raises: NodeNotFound if the node is not found.
:returns: a :class:`Node` object.
"""
db_node = cls.dbapi.get_node_by_port_addresses(addresses)
node = Node._from_db_object(cls(context), db_node)
return node

View File

@ -570,3 +570,57 @@ class DbNodeTestCase(base.DbTestCase):
self.assertRaises(
exception.NodeNotFound,
self.dbapi.touch_node_provisioning, uuidutils.generate_uuid())
def test_get_node_by_port_addresses(self):
wrong_node = utils.create_test_node(
driver='driver-one',
uuid=uuidutils.generate_uuid())
node = utils.create_test_node(
driver='driver-two',
uuid=uuidutils.generate_uuid())
addresses = []
for i in (1, 2, 3):
address = '52:54:00:cf:2d:4%s' % i
utils.create_test_port(uuid=uuidutils.generate_uuid(),
node_id=node.id, address=address)
if i > 1:
addresses.append(address)
utils.create_test_port(uuid=uuidutils.generate_uuid(),
node_id=wrong_node.id,
address='aa:bb:cc:dd:ee:ff')
res = self.dbapi.get_node_by_port_addresses(addresses)
self.assertEqual(node.uuid, res.uuid)
def test_get_node_by_port_addresses_not_found(self):
node = utils.create_test_node(
driver='driver',
uuid=uuidutils.generate_uuid())
utils.create_test_port(uuid=uuidutils.generate_uuid(),
node_id=node.id,
address='aa:bb:cc:dd:ee:ff')
self.assertRaisesRegexp(exception.NodeNotFound,
'was not found',
self.dbapi.get_node_by_port_addresses,
['11:22:33:44:55:66'])
def test_get_node_by_port_addresses_multiple_found(self):
node1 = utils.create_test_node(
driver='driver',
uuid=uuidutils.generate_uuid())
node2 = utils.create_test_node(
driver='driver',
uuid=uuidutils.generate_uuid())
addresses = ['52:54:00:cf:2d:4%s' % i for i in (1, 2)]
utils.create_test_port(uuid=uuidutils.generate_uuid(),
node_id=node1.id,
address=addresses[0])
utils.create_test_port(uuid=uuidutils.generate_uuid(),
node_id=node2.id,
address=addresses[1])
self.assertRaisesRegexp(exception.NodeNotFound,
'Multiple nodes',
self.dbapi.get_node_by_port_addresses,
addresses)

View File

@ -54,6 +54,17 @@ class TestNodeObject(base.DbTestCase):
self.assertRaises(exception.InvalidIdentity,
objects.Node.get, self.context, 'not-a-uuid')
def test_get_by_port_addresses(self):
with mock.patch.object(self.dbapi, 'get_node_by_port_addresses',
autospec=True) as mock_get_node:
mock_get_node.return_value = self.fake_node
node = objects.Node.get_by_port_addresses(self.context,
['aa:bb:cc:dd:ee:ff'])
mock_get_node.assert_called_once_with(['aa:bb:cc:dd:ee:ff'])
self.assertEqual(self.context, node._context)
def test_save(self):
uuid = self.fake_node['uuid']
with mock.patch.object(self.dbapi, 'get_node_by_uuid',

View File

@ -404,7 +404,7 @@ class TestObject(_LocalTest, _TestObject):
# version bump. It is md5 hash of object fields and remotable methods.
# The fingerprint values should only be changed if there is a version bump.
expected_object_fingerprints = {
'Node': '1.14-9ee8ab283b06398545880dfdedb49891',
'Node': '1.15-9ee8ab283b06398545880dfdedb49891',
'MyObj': '1.5-4f5efe8f0fcaf182bbe1c7fe3ba858db',
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
'Port': '1.5-a224755c3da5bc5cf1a14a11c0d00f3f',