Remove 'ComputeManager._reschedule'

Resolve a TODO and remove this function, folding its contents back into
its caller. This will make a future patch much simpler.

Part of blueprint remove-cells-v1

Change-Id: I02b8f07cb0a325cbb28603610f752041ef6cd02c
Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
Co-Authored-By: Balazs Gibizer <balazs.gibizer@est.tech>
This commit is contained in:
Stephen Finucane 2019-04-05 13:45:21 +01:00 committed by Matt Riedemann
parent d7bad34298
commit c419be6c17
4 changed files with 144 additions and 175 deletions

View File

@ -1422,37 +1422,6 @@ class ComputeManager(manager.Manager):
LOG.error('Error: %s', exc_info[1], instance_uuid=instance_uuid,
exc_info=exc_info)
# TODO(mriedem): This method is confusing and only ever used for resize
# reschedules; remove it and merge into _reschedule_resize_or_reraise.
def _reschedule(self, context, request_spec, filter_properties,
instance, reschedule_method, method_args, task_state,
exc_info=None, host_list=None):
"""Attempt to re-schedule a compute operation."""
instance_uuid = instance.uuid
retry = filter_properties.get('retry')
if not retry:
# no retry information, do not reschedule.
LOG.debug("Retry info not present, will not reschedule",
instance_uuid=instance_uuid)
return
LOG.debug("Re-scheduling %(method)s: attempt %(num)d",
{'method': reschedule_method.__name__,
'num': retry['num_attempts']}, instance_uuid=instance_uuid)
# reset the task state:
self._instance_update(context, instance, task_state=task_state)
if exc_info:
# stringify to avoid circular ref problem in json serialization:
retry['exc'] = traceback.format_exception_only(exc_info[0],
exc_info[1])
reschedule_method(context, *method_args, request_spec=request_spec,
host_list=host_list)
return True
@periodic_task.periodic_task
def _check_instance_build_time(self, context):
"""Ensure that instances are not stuck in build."""
@ -4458,14 +4427,33 @@ class ComputeManager(manager.Manager):
instance_uuid = instance.uuid
try:
reschedule_method = self.compute_task_api.resize_instance
scheduler_hint = dict(filter_properties=filter_properties)
method_args = (instance, None, scheduler_hint, instance_type)
task_state = task_states.RESIZE_PREP
retry = filter_properties.get('retry')
if retry:
LOG.debug('Rescheduling, attempt %d', retry['num_attempts'],
instance_uuid=instance_uuid)
rescheduled = self._reschedule(context, request_spec,
filter_properties, instance, reschedule_method,
method_args, task_state, exc_info, host_list=host_list)
# reset the task state
task_state = task_states.RESIZE_PREP
self._instance_update(context, instance, task_state=task_state)
if exc_info:
# stringify to avoid circular ref problem in json
# serialization
retry['exc'] = traceback.format_exception_only(
exc_info[0], exc_info[1])
scheduler_hint = {'filter_properties': filter_properties}
self.compute_task_api.resize_instance(
context, instance, None, scheduler_hint, instance_type,
request_spec=request_spec, host_list=host_list)
rescheduled = True
else:
# no retry information, do not reschedule.
LOG.debug('Retry info not present, will not reschedule',
instance_uuid=instance_uuid)
rescheduled = False
except Exception as error:
rescheduled = False
LOG.exception("Error trying to reschedule",
@ -4481,6 +4469,7 @@ class ComputeManager(manager.Manager):
phase=fields.NotificationPhase.ERROR,
exception=error,
tb=','.join(traceback.format_exception(*exc_info)))
if rescheduled:
self._log_original_error(exc_info, instance_uuid)
compute_utils.add_instance_fault_from_exc(context,

View File

@ -1056,11 +1056,9 @@ class TestInstanceNotificationSample(
'uuid': server['id']},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
@mock.patch('nova.compute.manager.ComputeManager._reschedule',
return_value=True)
@mock.patch('nova.compute.manager.ComputeManager._prep_resize')
def test_resize_server_error_but_reschedule_was_success(
self, mock_prep_resize, mock_reschedule):
self, mock_prep_resize):
"""Test it, when the prep_resize method raise an exception,
but the reschedule_resize_or_reraise was successful and
scheduled the resize. In this case we get a notification
@ -1089,6 +1087,14 @@ class TestInstanceNotificationSample(
}
fake_notifier.reset()
mock_prep_resize.side_effect = _build_resources
# NOTE(gibi): the first resize_instance call (from the API) should be
# unaffected so that we can reach _prep_resize at all. But the
# subsequent resize_instance call (from _reschedule_resize_or_reraise)
# needs to be mocked as there is no alternative host to resize to.
patcher = mock.patch.object(self.compute.manager.compute_task_api,
'resize_instance')
self.addCleanup(patcher.stop)
patcher.start()
self.api.post_server_action(server['id'], post)
self._wait_for_notification('instance.resize.error')
self._pop_and_verify_dest_select_notification(server['id'],
@ -1120,10 +1126,9 @@ class TestInstanceNotificationSample(
},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[2])
@mock.patch('nova.compute.manager.ComputeManager._reschedule')
@mock.patch('nova.compute.manager.ComputeManager._prep_resize')
def test_resize_server_error_and_reschedule_was_failed(
self, mock_prep_resize, mock_reschedule):
self, mock_prep_resize):
"""Test it, when the prep_resize method raise an exception,
after trying again with the reschedule_resize_or_reraise method
call, but the rescheduled also was unsuccessful. In this
@ -1156,9 +1161,17 @@ class TestInstanceNotificationSample(
}
fake_notifier.reset()
mock_prep_resize.side_effect = _build_resources
# This isn't realistic that _reschedule would raise FlavorDiskTooSmall,
# but it's needed for the notification sample to work.
mock_reschedule.side_effect = _build_resources
# NOTE(gibi): the first resize_instance call (from the API) should be
# unaffected so that we can reach _prep_resize at all. But the
# subsequent resize_instance call (from _reschedule_resize_or_reraise)
# needs to fail. It isn't realistic that resize_instance would raise
# FlavorDiskTooSmall, but it's needed for the notification sample
# to work.
patcher = mock.patch.object(self.compute.manager.compute_task_api,
'resize_instance',
side_effect=_build_resources)
self.addCleanup(patcher.stop)
patcher.start()
self.api.post_server_action(server['id'], post)
self._wait_for_state_change(self.api, server, expected_status='ERROR')
self._wait_for_notification('compute.exception')

View File

@ -22,7 +22,6 @@ from itertools import chain
import operator
import sys
import time
import traceback
import ddt
@ -12789,62 +12788,6 @@ class DisabledInstanceTypesTestCase(BaseTestCase):
self.compute_api.resize, self.context, instance, '4')
class ComputeReschedulingTestCase(BaseTestCase):
"""Tests re-scheduling logic for new build requests."""
def setUp(self):
super(ComputeReschedulingTestCase, self).setUp()
self.expected_task_state = task_states.SCHEDULING
def fake_update(*args, **kwargs):
self.updated_task_state = kwargs.get('task_state')
self.stub_out('nova.compute.manager.ComputeManager._instance_update',
fake_update)
def _reschedule(self, request_spec=None, filter_properties=None,
exc_info=None):
if not filter_properties:
filter_properties = {}
fake_taskapi = FakeComputeTaskAPI()
with mock.patch.object(self.compute, 'compute_task_api',
fake_taskapi):
instance = self._create_fake_instance_obj()
scheduler_method = self.compute.compute_task_api.resize_instance
method_args = (instance, None,
dict(filter_properties=filter_properties),
{}, None)
return self.compute._reschedule(self.context, request_spec,
filter_properties, instance, scheduler_method,
method_args, self.expected_task_state, exc_info=exc_info)
def test_reschedule_no_filter_properties(self):
# no filter_properties will disable re-scheduling.
self.assertFalse(self._reschedule())
def test_reschedule_no_retry_info(self):
# no retry info will also disable re-scheduling.
filter_properties = {}
self.assertFalse(self._reschedule(filter_properties=filter_properties))
def test_reschedule_success(self):
retry = dict(num_attempts=1)
filter_properties = dict(retry=retry)
request_spec = {'num_instances': 1}
try:
raise test.TestingException("just need an exception")
except test.TestingException:
exc_info = sys.exc_info()
exc_str = traceback.format_exception_only(exc_info[0],
exc_info[1])
self.assertTrue(self._reschedule(filter_properties=filter_properties,
request_spec=request_spec, exc_info=exc_info))
self.assertEqual(self.updated_task_state, self.expected_task_state)
self.assertEqual(exc_str, filter_properties['retry']['exc'])
class InnerTestingException(Exception):
pass
@ -12859,6 +12802,7 @@ class ComputeRescheduleResizeOrReraiseTestCase(BaseTestCase):
self.instance_uuid = self.instance['uuid']
self.instance_type = objects.Flavor.get_by_name(
context.get_admin_context(), 'm1.tiny')
self.request_spec = objects.RequestSpec()
@mock.patch('nova.compute.manager.ComputeManager._prep_resize',
side_effect=test.TestingException)
@ -12873,93 +12817,113 @@ class ComputeRescheduleResizeOrReraiseTestCase(BaseTestCase):
self.compute.prep_resize(self.context, image=None,
instance=inst_obj,
instance_type=self.instance_type,
request_spec=mock.sentinel.reqspec,
request_spec=self.request_spec,
filter_properties={}, migration=mock.Mock(),
node=None,
clean_shutdown=True, host_list=None)
mock_res.assert_called_once_with(mock.ANY, inst_obj, mock.ANY,
self.instance_type,
mock.sentinel.reqspec, {}, None)
self.request_spec, {}, None)
@mock.patch.object(compute_manager.ComputeManager, "_reschedule")
def test_reschedule_resize_or_reraise_no_filter_properties(self):
"""Test behavior when ``filter_properties`` is None.
This should disable rescheduling and the original exception should be
raised.
"""
filter_properties = None
try:
raise test.TestingException("Original")
except Exception:
exc_info = sys.exc_info()
# because we're not retrying, we should re-raise the exception
self.assertRaises(test.TestingException,
self.compute._reschedule_resize_or_reraise, self.context,
self.instance, exc_info, self.instance_type,
self.request_spec, filter_properties, None)
def test_reschedule_resize_or_reraise_no_retry_info(self):
"""Test behavior when ``filter_properties`` doesn't contain 'retry'.
This should disable rescheduling and the original exception should be
raised.
"""
filter_properties = {}
try:
raise test.TestingException("Original")
except Exception:
exc_info = sys.exc_info()
# because we're not retrying, we should re-raise the exception
self.assertRaises(test.TestingException,
self.compute._reschedule_resize_or_reraise, self.context,
self.instance, exc_info, self.instance_type,
self.request_spec, filter_properties, None)
@mock.patch.object(compute_manager.ComputeManager, '_instance_update')
@mock.patch('nova.conductor.api.ComputeTaskAPI.resize_instance',
side_effect=InnerTestingException('inner'))
@mock.patch('nova.compute.utils.notify_about_instance_action')
def test_reschedule_fails_with_exception(self, mock_notify, mock_res):
"""Original exception should be raised if the _reschedule method
raises another exception
"""
instance = self._create_fake_instance_obj()
scheduler_hint = dict(filter_properties={})
method_args = (instance, None, scheduler_hint, self.instance_type)
mock_res.side_effect = InnerTestingException("Inner")
try:
raise test.TestingException("Original")
except Exception:
exc_info = sys.exc_info()
reqspec = objects.RequestSpec()
self.assertRaises(test.TestingException,
self.compute._reschedule_resize_or_reraise, self.context,
instance, exc_info, self.instance_type, reqspec,
{}, None)
mock_res.assert_called_once_with(
self.context, reqspec, {}, instance,
self.compute.compute_task_api.resize_instance, method_args,
task_states.RESIZE_PREP, exc_info, host_list=None)
mock_notify.assert_called_once_with(
self.context, instance, 'fake-mini', action='resize',
phase='error', exception=mock_res.side_effect, tb=mock.ANY)
@mock.patch.object(compute_manager.ComputeManager, "_reschedule")
def test_reschedule_false(self, mock_res):
"""Original exception should be raised if the resize is not
rescheduled.
"""
instance = self._create_fake_instance_obj()
scheduler_hint = dict(filter_properties={})
method_args = (instance, None, scheduler_hint, self.instance_type)
mock_res.return_value = False
try:
raise test.TestingException("Original")
except Exception:
exc_info = sys.exc_info()
reqspec = objects.RequestSpec()
self.assertRaises(test.TestingException,
self.compute._reschedule_resize_or_reraise, self.context,
instance, exc_info, self.instance_type, reqspec,
{}, None)
mock_res.assert_called_once_with(
self.context, reqspec, {}, instance,
self.compute.compute_task_api.resize_instance, method_args,
task_states.RESIZE_PREP, exc_info, host_list=None)
@mock.patch.object(compute_manager.ComputeManager, "_reschedule")
@mock.patch.object(compute_manager.ComputeManager, "_log_original_error")
def test_reschedule_true(self, mock_log, mock_res):
# If rescheduled, the original resize exception should be logged.
instance = self._create_fake_instance_obj()
scheduler_hint = dict(filter_properties={})
method_args = (instance, None, scheduler_hint, self.instance_type)
def test_reschedule_fails_with_exception(self, mock_log, mock_notify,
mock_resize, mock_update):
"""Original exception should be raised if the conductor call
raises another exception.
"""
filter_properties = {'retry': {'num_attempts': 0}}
try:
raise test.TestingException('Original')
except Exception:
exc_info = sys.exc_info()
self.assertRaises(test.TestingException,
self.compute._reschedule_resize_or_reraise, self.context,
self.instance, exc_info, self.instance_type,
self.request_spec, filter_properties, None)
mock_update.assert_called_once_with(
self.context, mock.ANY, task_state=task_states.RESIZE_PREP)
mock_resize.assert_called_once_with(
self.context, mock.ANY, None,
{'filter_properties': filter_properties}, self.instance_type,
request_spec=self.request_spec, host_list=None)
mock_notify.assert_called_once_with(
self.context, self.instance, 'fake-mini', action='resize',
phase='error', exception=mock_resize.side_effect, tb=mock.ANY)
# If not rescheduled, the original resize exception should not be
# logged.
mock_log.assert_not_called()
@mock.patch.object(compute_manager.ComputeManager, '_instance_update')
@mock.patch('nova.conductor.api.ComputeTaskAPI.resize_instance')
@mock.patch('nova.compute.utils.notify_about_instance_action')
@mock.patch.object(compute_manager.ComputeManager, "_log_original_error")
def test_reschedule_passes(self, mock_log, mock_notify, mock_resize,
mock_update):
filter_properties = {'retry': {'num_attempts': 0}}
try:
raise test.TestingException("Original")
except Exception:
exc_info = sys.exc_info()
mock_res.return_value = True
reqspec = objects.RequestSpec()
self.compute._reschedule_resize_or_reraise(
self.context, instance, exc_info,
self.instance_type, reqspec, {}, None)
self.context, self.instance, exc_info, self.instance_type,
self.request_spec, filter_properties, None)
mock_res.assert_called_once_with(self.context, reqspec, {},
instance, self.compute.compute_task_api.resize_instance,
method_args, task_states.RESIZE_PREP, exc_info,
host_list=None)
mock_log.assert_called_once_with(exc_info, instance.uuid)
mock_update.assert_called_once_with(
self.context, mock.ANY, task_state=task_states.RESIZE_PREP)
mock_resize.assert_called_once_with(
self.context, mock.ANY, None,
{'filter_properties': filter_properties}, self.instance_type,
request_spec=self.request_spec, host_list=None)
mock_notify.assert_called_once_with(
self.context, self.instance, 'fake-mini', action='resize',
phase='error', exception=exc_info[1], tb=mock.ANY)
# If rescheduled, the original resize exception should be logged.
mock_log.assert_called_once_with(exc_info, self.instance.uuid)
class ComputeInactiveImageTestCase(BaseTestCase):

View File

@ -8754,13 +8754,13 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
reportclient = self.compute.reportclient
@mock.patch.object(self.compute, '_reschedule')
@mock.patch.object(self.compute, '_reschedule_resize_or_reraise')
@mock.patch.object(self.compute, '_prep_resize')
def doit(mock_pr, mock_r):
# Mock the resource tracker, but keep the report client
self._mock_rt().reportclient = reportclient
mock_r.return_value = False
mock_pr.side_effect = test.TestingException
mock_r.side_effect = test.TestingException
instance = objects.Instance(uuid=uuids.instance,
id=1,
@ -8781,6 +8781,9 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase,
self.assertEqual(migration.status, 'error')
migration.save.assert_called_once_with()
mock_r.assert_called_once_with(
self.context, instance, mock.ANY, flavor,
mock.sentinel.request_spec, {}, [])
mock_notify_resize.assert_has_calls([
mock.call(self.context, instance, 'fake-mini',
'start', flavor),