Execute TargetDBSetupTask

This adds the code to CrossCellMigrationTask which
executes the TargetDBSetupTask sub-task and stores
some fields on the main task for use later when
dealing with the target cell.

Part of blueprint cross-cell-resize

Change-Id: I00683acee216c0c0ad87be3eb5ec832f20f054c7
This commit is contained in:
Matt Riedemann
2019-01-29 19:11:43 -05:00
parent b910af3b41
commit e7e6dfb7de
2 changed files with 85 additions and 0 deletions
@@ -11,10 +11,12 @@
# under the License.
import collections
import copy
from oslo_log import log as logging
from nova.conductor.tasks import base
from nova import context as nova_context
from nova import exception
from nova.i18n import _
from nova import network
@@ -226,6 +228,9 @@ class CrossCellMigrationTask(base.TaskBase):
self.host_selection = host_selection
self.alternate_hosts = alternate_hosts
self._target_cell_instance = None
self._target_cell_context = None
self.network_api = network.API()
self.volume_api = cinder.API()
@@ -233,6 +238,41 @@ class CrossCellMigrationTask(base.TaskBase):
# rollback routines if something fails.
self._completed_tasks = collections.OrderedDict()
def _get_target_cell_mapping(self):
"""Get the target host CellMapping for the selected host
:returns: nova.objects.CellMapping for the cell of the selected target
host
:raises: nova.exception.CellMappingNotFound if the cell mapping for
the selected target host cannot be found (this should not happen
if the scheduler just selected it)
"""
return objects.CellMapping.get_by_uuid(
self.context, self.host_selection.cell_uuid)
def _setup_target_cell_db(self):
"""Creates the instance and its related records in the target cell
Upon successful completion the self._target_cell_context and
self._target_cell_instance variables are set.
:returns: The active Migration object from the target cell DB.
"""
LOG.debug('Setting up the target cell database for the instance and '
'its related records.', instance=self.instance)
target_cell_mapping = self._get_target_cell_mapping()
# Clone the context targeted at the source cell and then target the
# clone at the target cell.
self._target_cell_context = copy.copy(self.context)
nova_context.set_target_cell(
self._target_cell_context, target_cell_mapping)
task = TargetDBSetupTask(
self.context, self.instance, self.migration,
self._target_cell_context)
self._target_cell_instance, target_cell_migration = task.execute()
self._completed_tasks['TargetDBSetupTask'] = task
return target_cell_migration
def _perform_external_api_checks(self):
"""Performs checks on external service APIs for support.
@@ -268,6 +308,15 @@ class CrossCellMigrationTask(base.TaskBase):
# Make sure neutron and cinder APIs we need are available.
self._perform_external_api_checks()
# Before preparing the target host create the instance record data
# in the target cell database since we cannot do anything in the
# target cell without having an instance record there. Remember that
# we lose the cell-targeting on the request context over RPC so we
# cannot simply pass the source cell context and instance over RPC
# to the target compute host and assume changes get mirrored back to
# the source cell database.
self._setup_target_cell_db()
def rollback(self):
"""Rollback based on how sub-tasks completed
@@ -392,8 +392,10 @@ class CrossCellMigrationTaskTestCase(test.NoDBTestCase):
with test.nested(
mock.patch.object(self.task.migration, 'save'),
mock.patch.object(self.task, '_perform_external_api_checks'),
mock.patch.object(self.task, '_setup_target_cell_db'),
) as (
mock_migration_save, mock_perform_external_api_checks,
mock_setup_target_cell_db,
):
self.task.execute()
# Assert the calls
@@ -401,6 +403,7 @@ class CrossCellMigrationTaskTestCase(test.NoDBTestCase):
'Migration.cross_cell_move should be True.')
mock_migration_save.assert_called_once_with()
mock_perform_external_api_checks.assert_called_once_with()
mock_setup_target_cell_db.assert_called_once_with()
# Now rollback the completed sub-tasks
self.task.rollback()
@@ -467,3 +470,36 @@ class CrossCellMigrationTaskTestCase(test.NoDBTestCase):
# The 2nd task rollback should have raised and been logged.
mock_log_exception.assert_called_once()
self.assertEqual('1', mock_log_exception.call_args[0][1])
@mock.patch('nova.objects.CellMapping.get_by_uuid')
@mock.patch('nova.context.set_target_cell')
@mock.patch.object(cross_cell_migrate.TargetDBSetupTask, 'execute')
def test_setup_target_cell_db(self, mock_target_db_set_task_execute,
mock_set_target_cell, mock_get_cell_mapping):
"""Tests setting up and executing TargetDBSetupTask"""
mock_target_db_set_task_execute.return_value = (
mock.sentinel.target_cell_instance,
mock.sentinel.target_cell_migration)
result = self.task._setup_target_cell_db()
mock_target_db_set_task_execute.assert_called_once_with()
mock_get_cell_mapping.assert_called_once_with(
self.task.context, self.task.host_selection.cell_uuid)
# The target_cell_context should be set on the main task but as a copy
# of the source context.
self.assertIsNotNone(self.task._target_cell_context)
self.assertIsNot(self.task._target_cell_context, self.task.context)
# The target cell context should have been targeted to the target
# cell mapping.
mock_set_target_cell.assert_called_once_with(
self.task._target_cell_context, mock_get_cell_mapping.return_value)
# The resulting migration record from TargetDBSetupTask should have
# been returned.
self.assertIs(result, mock.sentinel.target_cell_migration)
# The target_cell_instance should be set on the main task.
self.assertIsNotNone(self.task._target_cell_instance)
self.assertIs(self.task._target_cell_instance,
mock.sentinel.target_cell_instance)
# And the completed task should have been recorded for rollbacks.
self.assertIn('TargetDBSetupTask', self.task._completed_tasks)
self.assertIsInstance(self.task._completed_tasks['TargetDBSetupTask'],
cross_cell_migrate.TargetDBSetupTask)