ironic/ironic/tests/unit/conductor/test_periodics.py
Dmitry Tantsur cf1b42ea3d Add a helper for node-based periodics
We have a very common pattern of periodic tasks that use iter_nodes
to fetch some nodes, check them, create a task and conductor some
operation. This change introduces a helper decorator for that and
migrates the drivers to it.

I'm intentionally leaving unit tests intact to demonstrate that the
new decorator works exactly the same way (modulo cosmetic changes)
as the previous hand-written code.

Change-Id: Ifed4a457275d9451cc412dc80f3c09df72f50492
Story: #2009203
Task: #43522
2021-10-11 17:26:06 +02:00

136 lines
5.2 KiB
Python

# 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.
from unittest import mock
from oslo_utils import uuidutils
from ironic.common import context as ironic_context
from ironic.conductor import base_manager
from ironic.conductor import periodics
from ironic.conductor import task_manager
from ironic.tests.unit.db import base as db_base
from ironic.tests.unit.objects import utils as obj_utils
_FILTERS = {'maintenance': False}
class PeriodicTestService(base_manager.BaseConductorManager):
def __init__(self, test):
self.test = test
self.nodes = []
@periodics.node_periodic(purpose="herding cats", spacing=42)
def simple(self, task, context):
self.test.assertIsInstance(context, ironic_context.RequestContext)
self.test.assertTrue(task.shared)
self.nodes.append(task.node.uuid)
@periodics.node_periodic(purpose="herding cats", spacing=42,
shared_task=False, filters=_FILTERS)
def exclusive(self, task, context):
self.test.assertIsInstance(context, ironic_context.RequestContext)
self.test.assertFalse(task.shared)
self.nodes.append(task.node.uuid)
@periodics.node_periodic(purpose="never running", spacing=42,
predicate=lambda n: n.cat != 'meow',
predicate_extra_fields=['cat'])
def never_run(self, task, context):
self.test.fail(f"Was not supposed to run, ran with {task.node}")
@periodics.node_periodic(purpose="herding cats", spacing=42, limit=3)
def limit(self, task, context):
self.test.assertIsInstance(context, ironic_context.RequestContext)
self.test.assertTrue(task.shared)
self.nodes.append(task.node.uuid)
if task.node.uuid == 'stop':
raise periodics.Stop()
@mock.patch.object(PeriodicTestService, 'iter_nodes', autospec=True)
class NodePeriodicTestCase(db_base.DbTestCase):
def setUp(self):
super().setUp()
self.service = PeriodicTestService(self)
self.ctx = ironic_context.get_admin_context()
self.uuid = uuidutils.generate_uuid()
self.node = obj_utils.create_test_node(self.context, uuid=self.uuid)
def test_simple(self, mock_iter_nodes):
mock_iter_nodes.return_value = iter([
(uuidutils.generate_uuid(), 'driver1', ''),
(self.uuid, 'driver2', 'group'),
])
self.service.simple(self.ctx)
mock_iter_nodes.assert_called_once_with(self.service,
filters=None, fields=())
self.assertEqual([self.uuid], self.service.nodes)
def test_exclusive(self, mock_iter_nodes):
mock_iter_nodes.return_value = iter([
(uuidutils.generate_uuid(), 'driver1', ''),
(self.uuid, 'driver2', 'group'),
])
self.service.exclusive(self.ctx)
mock_iter_nodes.assert_called_once_with(self.service,
filters=_FILTERS,
fields=())
self.assertEqual([self.uuid], self.service.nodes)
@mock.patch.object(task_manager, 'acquire', autospec=True)
def test_never_run(self, mock_acquire, mock_iter_nodes):
mock_iter_nodes.return_value = iter([
(self.uuid, 'driver2', 'group', 'meow'),
])
self.service.never_run(self.ctx)
mock_iter_nodes.assert_called_once_with(self.service,
filters=None,
fields=['cat'])
self.assertEqual([], self.service.nodes)
mock_acquire.assert_not_called()
@mock.patch.object(task_manager, 'acquire', autospec=True)
def test_limit(self, mock_acquire, mock_iter_nodes):
mock_iter_nodes.return_value = iter([
(self.uuid, 'driver1', ''),
] * 10)
mock_acquire.return_value.__enter__.return_value.node.uuid = self.uuid
self.service.limit(self.ctx)
mock_iter_nodes.assert_called_once_with(self.service,
filters=None, fields=())
self.assertEqual([self.uuid] * 3, self.service.nodes)
@mock.patch.object(task_manager, 'acquire', autospec=True)
def test_stop(self, mock_acquire, mock_iter_nodes):
mock_iter_nodes.return_value = iter([
(self.uuid, 'driver1', ''),
] * 10)
mock_acquire.return_value.__enter__.return_value.node.uuid = 'stop'
self.service.limit(self.ctx)
mock_iter_nodes.assert_called_once_with(self.service,
filters=None, fields=())
self.assertEqual(['stop'], self.service.nodes)