Add reserve() and release() to Node object
What it says. Also make the code and tests use them vs direct calls to dbapi. Change-Id: I0158be53ff683ec3569007bc6f0cee6395241062 Partial-Bug: #1314732
This commit is contained in:
parent
526bb53528
commit
f575ae7ea3
|
@ -93,7 +93,6 @@ from oslo.utils import excutils
|
|||
from ironic.common import driver_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _LW
|
||||
from ironic.db import api as dbapi
|
||||
from ironic import objects
|
||||
from ironic.openstack.common import log as logging
|
||||
|
||||
|
@ -161,7 +160,6 @@ class TaskManager(object):
|
|||
|
||||
"""
|
||||
|
||||
self._dbapi = dbapi.get_instance()
|
||||
self._spawn_method = None
|
||||
self._on_error_method = None
|
||||
|
||||
|
@ -179,7 +177,7 @@ class TaskManager(object):
|
|||
def reserve_node():
|
||||
LOG.debug("Attempting to reserve node %(node)s",
|
||||
{'node': node_id})
|
||||
self.node = self._dbapi.reserve_node(CONF.host, node_id)
|
||||
self.node = objects.Node.reserve(context, CONF.host, node_id)
|
||||
|
||||
try:
|
||||
if not self.shared:
|
||||
|
@ -226,7 +224,7 @@ class TaskManager(object):
|
|||
if not self.shared:
|
||||
try:
|
||||
if self.node:
|
||||
self._dbapi.release_node(CONF.host, self.node.id)
|
||||
objects.Node.release(self.context, CONF.host, self.node.id)
|
||||
except exception.NodeNotFound:
|
||||
# squelch the exception if the node was deleted
|
||||
# within the task's context.
|
||||
|
|
|
@ -32,7 +32,6 @@ from ironic.common import states
|
|||
from ironic.common import utils
|
||||
from ironic.db import api
|
||||
from ironic.db.sqlalchemy import models
|
||||
from ironic import objects
|
||||
from ironic.openstack.common import log
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
@ -213,7 +212,6 @@ class Connection(api.Connection):
|
|||
return _paginate_query(models.Node, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
@objects.objectify(objects.Node)
|
||||
def reserve_node(self, tag, node_id):
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
|
|
|
@ -28,7 +28,8 @@ class Node(base.IronicObject):
|
|||
# Version 1.3: Add create() and destroy()
|
||||
# Version 1.4: Add get_by_instance_uuid()
|
||||
# Version 1.5: Add list()
|
||||
VERSION = '1.5'
|
||||
# Version 1.6: Add reserve() and release()
|
||||
VERSION = '1.6'
|
||||
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
|
@ -158,6 +159,39 @@ class Node(base.IronicObject):
|
|||
node_list.append(node)
|
||||
return node_list
|
||||
|
||||
@base.remotable_classmethod
|
||||
def reserve(cls, context, tag, node_id):
|
||||
"""Get and reserve a node.
|
||||
|
||||
To prevent other ManagerServices from manipulating the given
|
||||
Node while a Task is performed, mark it reserved by this host.
|
||||
|
||||
:param context: Security context.
|
||||
:param tag: A string uniquely identifying the reservation holder.
|
||||
:param node_id: A node id or uuid.
|
||||
:raises: NodeNotFound if the node is not found.
|
||||
:returns: a :class:`Node` object.
|
||||
|
||||
"""
|
||||
db_node = cls.dbapi.reserve_node(tag, node_id)
|
||||
node = Node._from_db_object(cls(), db_node)
|
||||
# FIXME(comstud): Setting of the context should be moved to
|
||||
# _from_db_object().
|
||||
node._context = context
|
||||
return node
|
||||
|
||||
@base.remotable_classmethod
|
||||
def release(cls, context, tag, node_id):
|
||||
"""Release the reservation on a node.
|
||||
|
||||
:param context: Security context.
|
||||
:param tag: A string uniquely identifying the reservation holder.
|
||||
:param node_id: A node id or uuid.
|
||||
:raises: NodeNotFound if the node is not found.
|
||||
|
||||
"""
|
||||
cls.dbapi.release_node(tag, node_id)
|
||||
|
||||
@base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a Node record in the DB.
|
||||
|
|
|
@ -23,15 +23,16 @@ import mock
|
|||
|
||||
from ironic.common import driver_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.db import api as dbapi
|
||||
from ironic import objects
|
||||
from ironic.tests import base as tests_base
|
||||
from ironic.tests.objects import utils as obj_utils
|
||||
|
||||
|
||||
@mock.patch.object(objects.Node, 'get')
|
||||
@mock.patch.object(dbapi.IMPL, 'release_node')
|
||||
@mock.patch.object(dbapi.IMPL, 'reserve_node')
|
||||
@mock.patch.object(objects.Node, 'release')
|
||||
@mock.patch.object(objects.Node, 'reserve')
|
||||
@mock.patch.object(driver_factory, 'get_driver')
|
||||
@mock.patch.object(objects.Port, 'list_by_node_id')
|
||||
class TaskManagerTestCase(tests_base.TestCase):
|
||||
|
@ -42,7 +43,7 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
self.config(node_locked_retry_attempts=1, group='conductor')
|
||||
self.config(node_locked_retry_interval=0, group='conductor')
|
||||
self.context = mock.sentinel.context
|
||||
self.node = mock.Mock(spec_set=objects.Node)
|
||||
self.node = obj_utils.create_test_node(self.context)
|
||||
|
||||
def test_excl_lock(self, get_ports_mock, get_driver_mock,
|
||||
reserve_mock, release_mock, node_get_mock):
|
||||
|
@ -54,10 +55,12 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
self.assertEqual(get_driver_mock.return_value, task.driver)
|
||||
self.assertFalse(task.shared)
|
||||
|
||||
reserve_mock.assert_called_once_with(self.host, 'fake-node-id')
|
||||
reserve_mock.assert_called_once_with(self.context, self.host,
|
||||
'fake-node-id')
|
||||
get_ports_mock.assert_called_once_with(self.context, self.node.id)
|
||||
get_driver_mock.assert_called_once_with(self.node.driver)
|
||||
release_mock.assert_called_once_with(self.host, self.node.id)
|
||||
release_mock.assert_called_once_with(self.context, self.host,
|
||||
self.node.id)
|
||||
self.assertFalse(node_get_mock.called)
|
||||
|
||||
def test_excl_lock_with_driver(self, get_ports_mock, get_driver_mock,
|
||||
|
@ -72,16 +75,21 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
self.assertEqual(get_driver_mock.return_value, task.driver)
|
||||
self.assertFalse(task.shared)
|
||||
|
||||
reserve_mock.assert_called_once_with(self.host, 'fake-node-id')
|
||||
reserve_mock.assert_called_once_with(self.context, self.host,
|
||||
'fake-node-id')
|
||||
get_ports_mock.assert_called_once_with(self.context, self.node.id)
|
||||
get_driver_mock.assert_called_once_with('fake-driver')
|
||||
release_mock.assert_called_once_with(self.host, self.node.id)
|
||||
release_mock.assert_called_once_with(self.context, self.host,
|
||||
self.node.id)
|
||||
self.assertFalse(node_get_mock.called)
|
||||
|
||||
def test_excl_nested_acquire(self, get_ports_mock, get_driver_mock,
|
||||
reserve_mock, release_mock,
|
||||
node_get_mock):
|
||||
node2 = mock.Mock(spec_set=objects.Node)
|
||||
node2 = obj_utils.create_test_node(self.context,
|
||||
id=2,
|
||||
uuid=utils.generate_uuid(),
|
||||
driver='fake')
|
||||
|
||||
reserve_mock.return_value = self.node
|
||||
get_ports_mock.return_value = mock.sentinel.ports1
|
||||
|
@ -103,8 +111,8 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
self.assertEqual(mock.sentinel.driver2, task2.driver)
|
||||
self.assertFalse(task2.shared)
|
||||
|
||||
self.assertEqual([mock.call(self.host, 'node-id1'),
|
||||
mock.call(self.host, 'node-id2')],
|
||||
self.assertEqual([mock.call(self.context, self.host, 'node-id1'),
|
||||
mock.call(self.context, self.host, 'node-id2')],
|
||||
reserve_mock.call_args_list)
|
||||
self.assertEqual([mock.call(self.context, self.node.id),
|
||||
mock.call(self.context, node2.id)],
|
||||
|
@ -113,8 +121,8 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
mock.call(node2.driver)],
|
||||
get_driver_mock.call_args_list)
|
||||
# release should be in reverse order
|
||||
self.assertEqual([mock.call(self.host, node2.id),
|
||||
mock.call(self.host, self.node.id)],
|
||||
self.assertEqual([mock.call(self.context, self.host, node2.id),
|
||||
mock.call(self.context, self.host, self.node.id)],
|
||||
release_mock.call_args_list)
|
||||
self.assertFalse(node_get_mock.called)
|
||||
|
||||
|
@ -133,7 +141,7 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
with task_manager.TaskManager(self.context, 'fake-node-id') as task:
|
||||
self.assertFalse(task.shared)
|
||||
|
||||
reserve_mock.assert_called(self.host, 'fake-node-id')
|
||||
reserve_mock.assert_called(self.context, self.host, 'fake-node-id')
|
||||
self.assertEqual(2, reserve_mock.call_count)
|
||||
|
||||
def test_excl_lock_reserve_exception(self, get_ports_mock,
|
||||
|
@ -150,7 +158,8 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
self.context,
|
||||
'fake-node-id')
|
||||
|
||||
reserve_mock.assert_called_with(self.host, 'fake-node-id')
|
||||
reserve_mock.assert_called_with(self.context, self.host,
|
||||
'fake-node-id')
|
||||
self.assertEqual(retry_attempts, reserve_mock.call_count)
|
||||
self.assertFalse(get_ports_mock.called)
|
||||
self.assertFalse(get_driver_mock.called)
|
||||
|
@ -168,10 +177,12 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
self.context,
|
||||
'fake-node-id')
|
||||
|
||||
reserve_mock.assert_called_once_with(self.host, 'fake-node-id')
|
||||
reserve_mock.assert_called_once_with(self.context, self.host,
|
||||
'fake-node-id')
|
||||
get_ports_mock.assert_called_once_with(self.context, self.node.id)
|
||||
self.assertFalse(get_driver_mock.called)
|
||||
release_mock.assert_called_once_with(self.host, self.node.id)
|
||||
release_mock.assert_called_once_with(self.context, self.host,
|
||||
self.node.id)
|
||||
self.assertFalse(node_get_mock.called)
|
||||
|
||||
def test_excl_lock_get_driver_exception(self, get_ports_mock,
|
||||
|
@ -186,10 +197,12 @@ class TaskManagerTestCase(tests_base.TestCase):
|
|||
self.context,
|
||||
'fake-node-id')
|
||||
|
||||
reserve_mock.assert_called_once_with(self.host, 'fake-node-id')
|
||||
reserve_mock.assert_called_once_with(self.context, self.host,
|
||||
'fake-node-id')
|
||||
get_ports_mock.assert_called_once_with(self.context, self.node.id)
|
||||
get_driver_mock.assert_called_once_with(self.node.driver)
|
||||
release_mock.assert_called_once_with(self.host, self.node.id)
|
||||
release_mock.assert_called_once_with(self.context, self.host,
|
||||
self.node.id)
|
||||
self.assertFalse(node_get_mock.called)
|
||||
|
||||
def test_shared_lock(self, get_ports_mock, get_driver_mock,
|
||||
|
|
|
@ -142,3 +142,39 @@ class TestNodeObject(base.DbTestCase):
|
|||
nodes = objects.Node.list(self.context)
|
||||
self.assertThat(nodes, HasLength(1))
|
||||
self.assertIsInstance(nodes[0], objects.Node)
|
||||
|
||||
def test_reserve(self):
|
||||
with mock.patch.object(self.dbapi, 'reserve_node',
|
||||
autospec=True) as mock_reserve:
|
||||
mock_reserve.return_value = self.fake_node
|
||||
node_id = self.fake_node['id']
|
||||
fake_tag = 'fake-tag'
|
||||
node = objects.Node.reserve(self.context, fake_tag, node_id)
|
||||
self.assertIsInstance(node, objects.Node)
|
||||
mock_reserve.assert_called_once_with(fake_tag, node_id)
|
||||
|
||||
def test_reserve_node_not_found(self):
|
||||
with mock.patch.object(self.dbapi, 'reserve_node',
|
||||
autospec=True) as mock_reserve:
|
||||
node_id = 'non-existent'
|
||||
mock_reserve.side_effect = exception.NodeNotFound(node=node_id)
|
||||
self.assertRaises(exception.NodeNotFound,
|
||||
objects.Node.reserve, self.context, 'fake-tag',
|
||||
node_id)
|
||||
|
||||
def test_release(self):
|
||||
with mock.patch.object(self.dbapi, 'release_node',
|
||||
autospec=True) as mock_release:
|
||||
node_id = self.fake_node['id']
|
||||
fake_tag = 'fake-tag'
|
||||
objects.Node.release(self.context, fake_tag, node_id)
|
||||
mock_release.assert_called_once_with(fake_tag, node_id)
|
||||
|
||||
def test_release_node_not_found(self):
|
||||
with mock.patch.object(self.dbapi, 'release_node',
|
||||
autospec=True) as mock_release:
|
||||
node_id = 'non-existent'
|
||||
mock_release.side_effect = exception.NodeNotFound(node=node_id)
|
||||
self.assertRaises(exception.NodeNotFound,
|
||||
objects.Node.release, self.context,
|
||||
'fake-tag', node_id)
|
||||
|
|
Loading…
Reference in New Issue