70516d4ff9
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
355 lines
15 KiB
Python
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'
|