Files
nova/nova/tests/unit/api/openstack/compute/test_server_migrations.py
T
Dan Smith 70516d4ff9 Add dest_compute_id to Migration object
This makes us store the compute_id of the destination node in the
Migration object. Since resize/cold-migration changes the node
affiliation of an instance *to* the destination node *from* the source
node, we need a positive record of the node id to be used. The
destination node set this to its own node when creating the migration,
and it is used by the source node when the switchover happens.

Because the migration may be backleveled for an older node involved
in that process and thus saved or passed without this field, this
adds a compatibility routine that falls back to looking up the node
by host/nodename.

Related to blueprint compute-object-ids

Change-Id: I362a40403d1094be36412f5f7afba00da8af8301
2023-05-31 07:13:16 -07:00

355 lines
15 KiB
Python

# Copyright 2016 OpenStack Foundation
# All Rights Reserved.
#
# 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.
import copy
import datetime
from unittest import mock
from oslo_utils.fixture import uuidsentinel as uuids
import webob
from nova.api.openstack.compute import server_migrations
from nova import exception
from nova import objects
from nova.objects import base
from nova import test
from nova.tests.unit.api.openstack import fakes
SERVER_UUID = uuids.server_uuid
fake_migrations = [
{
'id': 1234,
'source_node': 'node1',
'dest_node': 'node2',
'dest_compute_id': 123,
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'running',
'instance_uuid': SERVER_UUID,
'old_instance_type_id': 1,
'new_instance_type_id': 2,
'migration_type': 'live-migration',
'hidden': False,
'memory_total': 123456,
'memory_processed': 12345,
'memory_remaining': 111111,
'disk_total': 234567,
'disk_processed': 23456,
'disk_remaining': 211111,
'created_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'deleted_at': None,
'deleted': False,
'uuid': uuids.migration1,
'cross_cell_move': False,
'user_id': None,
'project_id': None
},
{
'id': 5678,
'source_node': 'node10',
'dest_node': 'node20',
'dest_compute_id': 123,
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'running',
'instance_uuid': SERVER_UUID,
'old_instance_type_id': 5,
'new_instance_type_id': 6,
'migration_type': 'live-migration',
'hidden': False,
'memory_total': 456789,
'memory_processed': 56789,
'memory_remaining': 400000,
'disk_total': 96789,
'disk_processed': 6789,
'disk_remaining': 90000,
'created_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'updated_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'deleted_at': None,
'deleted': False,
'uuid': uuids.migration2,
'cross_cell_move': False,
'user_id': None,
'project_id': None
}
]
migrations_obj = base.obj_make_list(
'fake-context',
objects.MigrationList(),
objects.Migration,
fake_migrations)
class ServerMigrationsTestsV21(test.NoDBTestCase):
wsgi_api_version = '2.22'
def setUp(self):
super(ServerMigrationsTestsV21, self).setUp()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
self.context = self.req.environ['nova.context']
self.controller = server_migrations.ServerMigrationsController()
self.compute_api = self.controller.compute_api
def test_force_complete_succeeded(self):
@mock.patch.object(self.compute_api, 'live_migrate_force_complete')
@mock.patch.object(self.compute_api, 'get')
def _do_test(compute_api_get, live_migrate_force_complete):
self.controller._force_complete(self.req, '1', '1',
body={'force_complete': None})
live_migrate_force_complete.assert_called_once_with(
self.context, compute_api_get.return_value, '1')
_do_test()
def _test_force_complete_failed_with_exception(self, fake_exc,
expected_exc):
@mock.patch.object(self.compute_api, 'live_migrate_force_complete',
side_effect=fake_exc)
@mock.patch.object(self.compute_api, 'get')
def _do_test(compute_api_get, live_migrate_force_complete):
self.assertRaises(expected_exc,
self.controller._force_complete,
self.req, '1', '1',
body={'force_complete': None})
_do_test()
def test_force_complete_instance_not_migrating(self):
self._test_force_complete_failed_with_exception(
exception.InstanceInvalidState(instance_uuid='', state='',
attr='', method=''),
webob.exc.HTTPConflict)
def test_force_complete_migration_not_found(self):
self._test_force_complete_failed_with_exception(
exception.MigrationNotFoundByStatus(instance_id='', status=''),
webob.exc.HTTPBadRequest)
def test_force_complete_instance_is_locked(self):
self._test_force_complete_failed_with_exception(
exception.InstanceIsLocked(instance_uuid=''),
webob.exc.HTTPConflict)
def test_force_complete_invalid_migration_state(self):
self._test_force_complete_failed_with_exception(
exception.InvalidMigrationState(migration_id='', instance_uuid='',
state='', method=''),
webob.exc.HTTPBadRequest)
def test_force_complete_instance_not_found(self):
self._test_force_complete_failed_with_exception(
exception.InstanceNotFound(instance_id=''),
webob.exc.HTTPNotFound)
def test_force_complete_unexpected_error(self):
self._test_force_complete_failed_with_exception(
exception.NovaException(),
webob.exc.HTTPInternalServerError)
class ServerMigrationsTestsV223(ServerMigrationsTestsV21):
wsgi_api_version = '2.23'
def setUp(self):
super(ServerMigrationsTestsV223, self).setUp()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version,
use_admin_context=True)
self.context = self.req.environ['nova.context']
@mock.patch('nova.compute.api.API.get_migrations_in_progress_by_instance')
@mock.patch('nova.compute.api.API.get')
def test_index(self, m_get_instance, m_get_mig):
migrations = [server_migrations.output(mig) for mig in migrations_obj]
migrations_in_progress = {'migrations': migrations}
for mig in migrations_in_progress['migrations']:
self.assertIn('id', mig)
self.assertNotIn('deleted', mig)
self.assertNotIn('deleted_at', mig)
m_get_mig.return_value = migrations_obj
response = self.controller.index(self.req, SERVER_UUID)
self.assertEqual(migrations_in_progress, response)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
cell_down_support=False)
@mock.patch('nova.compute.api.API.get')
def test_index_invalid_instance(self, m_get_instance):
m_get_instance.side_effect = exception.InstanceNotFound(instance_id=1)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.index,
self.req, SERVER_UUID)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
cell_down_support=False)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show(self, m_get_instance, m_get_mig):
migrations = [server_migrations.output(mig) for mig in migrations_obj]
m_get_mig.return_value = migrations_obj[0]
response = self.controller.show(self.req, SERVER_UUID,
migrations_obj[0].id)
self.assertEqual(migrations[0], response['migration'])
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
cell_down_support=False)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show_migration_non_progress(self, m_get_instance, m_get_mig):
non_progress_mig = copy.deepcopy(migrations_obj[0])
non_progress_mig.status = "reverted"
m_get_mig.return_value = non_progress_mig
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
non_progress_mig.id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
cell_down_support=False)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show_migration_not_live_migration(self, m_get_instance,
m_get_mig):
non_progress_mig = copy.deepcopy(migrations_obj[0])
non_progress_mig.migration_type = "resize"
m_get_mig.return_value = non_progress_mig
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
non_progress_mig.id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
cell_down_support=False)
@mock.patch('nova.compute.api.API.get_migration_by_id_and_instance')
@mock.patch('nova.compute.api.API.get')
def test_show_migration_not_exist(self, m_get_instance, m_get_mig):
m_get_mig.side_effect = exception.MigrationNotFoundForInstance(
migration_id=migrations_obj[0].id,
instance_id=SERVER_UUID)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
migrations_obj[0].id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
cell_down_support=False)
@mock.patch('nova.compute.api.API.get')
def test_show_migration_invalid_instance(self, m_get_instance):
m_get_instance.side_effect = exception.InstanceNotFound(instance_id=1)
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.show,
self.req, SERVER_UUID,
migrations_obj[0].id)
m_get_instance.assert_called_once_with(self.context, SERVER_UUID,
expected_attrs=None,
cell_down_support=False)
class ServerMigrationsTestsV224(ServerMigrationsTestsV21):
wsgi_api_version = '2.24'
def setUp(self):
super(ServerMigrationsTestsV224, self).setUp()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version,
use_admin_context=True)
self.context = self.req.environ['nova.context']
def test_cancel_live_migration_succeeded(self):
@mock.patch.object(self.compute_api, 'live_migrate_abort')
@mock.patch.object(self.compute_api, 'get')
def _do_test(mock_get, mock_abort):
self.controller.delete(self.req, 'server-id', 'migration-id')
mock_abort.assert_called_once_with(self.context,
mock_get.return_value,
'migration-id',
support_abort_in_queue=False)
_do_test()
def _test_cancel_live_migration_failed(self, fake_exc, expected_exc):
@mock.patch.object(self.compute_api, 'live_migrate_abort',
side_effect=fake_exc)
@mock.patch.object(self.compute_api, 'get')
def _do_test(mock_get, mock_abort):
self.assertRaises(expected_exc,
self.controller.delete,
self.req,
'server-id',
'migration-id')
_do_test()
def test_cancel_live_migration_invalid_state(self):
self._test_cancel_live_migration_failed(
exception.InstanceInvalidState(instance_uuid='',
state='',
attr='',
method=''),
webob.exc.HTTPConflict)
def test_cancel_live_migration_migration_not_found(self):
self._test_cancel_live_migration_failed(
exception.MigrationNotFoundForInstance(migration_id='',
instance_id=''),
webob.exc.HTTPNotFound)
def test_cancel_live_migration_invalid_migration_state(self):
self._test_cancel_live_migration_failed(
exception.InvalidMigrationState(migration_id='',
instance_uuid='',
state='',
method=''),
webob.exc.HTTPBadRequest)
def test_cancel_live_migration_instance_not_found(self):
self.assertRaises(webob.exc.HTTPNotFound,
self.controller.delete,
self.req,
'server-id',
'migration-id')
class ServerMigrationsTestsV265(ServerMigrationsTestsV224):
wsgi_api_version = '2.65'
def test_cancel_live_migration_succeeded(self):
@mock.patch.object(self.compute_api, 'live_migrate_abort')
@mock.patch.object(self.compute_api, 'get')
def _do_test(mock_get, mock_abort):
self.controller.delete(self.req, 'server-id', 1)
mock_abort.assert_called_once_with(self.context,
mock_get.return_value, 1,
support_abort_in_queue=True)
_do_test()
class ServerMigrationsTestsV280(ServerMigrationsTestsV265):
wsgi_api_version = '2.80'