412 lines
17 KiB
Python
412 lines
17 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
|
|
|
|
import mock
|
|
import six
|
|
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
|
|
from nova.tests import uuidsentinel as uuids
|
|
|
|
SERVER_UUID = uuids.server_uuid
|
|
|
|
fake_migrations = [
|
|
{
|
|
'id': 1234,
|
|
'source_node': 'node1',
|
|
'dest_node': 'node2',
|
|
'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,
|
|
},
|
|
{
|
|
'id': 5678,
|
|
'source_node': 'node10',
|
|
'dest_node': 'node20',
|
|
'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,
|
|
}
|
|
]
|
|
|
|
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(), '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)
|
|
|
|
@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)
|
|
|
|
@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)
|
|
|
|
@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)
|
|
|
|
@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)
|
|
|
|
@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)
|
|
|
|
@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)
|
|
|
|
|
|
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(),
|
|
'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()
|
|
|
|
def test_cancel_live_migration_in_queue_not_yet_available(self):
|
|
exc = exception.AbortQueuedLiveMigrationNotYetSupported(
|
|
migration_id=1, status='queued')
|
|
|
|
@mock.patch.object(self.compute_api, 'live_migrate_abort',
|
|
side_effect=exc)
|
|
@mock.patch.object(self.compute_api, 'get')
|
|
def _do_test(mock_get, mock_abort):
|
|
error = self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller.delete,
|
|
self.req, 'server-id', 1)
|
|
self.assertIn("Aborting live migration 1 with status queued is "
|
|
"not yet supported for this instance.",
|
|
six.text_type(error))
|
|
mock_abort.assert_called_once_with(self.context,
|
|
mock_get.return_value, 1,
|
|
support_abort_in_queue=True)
|
|
_do_test()
|
|
|
|
|
|
class ServerMigrationsPolicyEnforcementV21(test.NoDBTestCase):
|
|
wsgi_api_version = '2.22'
|
|
|
|
def setUp(self):
|
|
super(ServerMigrationsPolicyEnforcementV21, self).setUp()
|
|
self.controller = server_migrations.ServerMigrationsController()
|
|
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
|
|
|
def test_force_complete_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:migrations:force_complete"
|
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
|
body_args = {'force_complete': None}
|
|
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller._force_complete, self.req,
|
|
fakes.FAKE_UUID, fakes.FAKE_UUID,
|
|
body=body_args)
|
|
self.assertEqual(
|
|
"Policy doesn't allow %s to be performed." % rule_name,
|
|
exc.format_message())
|
|
|
|
|
|
class ServerMigrationsPolicyEnforcementV223(
|
|
ServerMigrationsPolicyEnforcementV21):
|
|
|
|
wsgi_api_version = '2.23'
|
|
|
|
def test_migration_index_failed(self):
|
|
rule_name = "os_compute_api:servers:migrations:index"
|
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
|
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller.index, self.req,
|
|
fakes.FAKE_UUID)
|
|
self.assertEqual("Policy doesn't allow %s to be performed." %
|
|
rule_name, exc.format_message())
|
|
|
|
def test_migration_show_failed(self):
|
|
rule_name = "os_compute_api:servers:migrations:show"
|
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
|
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller.show, self.req,
|
|
fakes.FAKE_UUID, 1)
|
|
self.assertEqual("Policy doesn't allow %s to be performed." %
|
|
rule_name, exc.format_message())
|
|
|
|
|
|
class ServerMigrationsPolicyEnforcementV224(
|
|
ServerMigrationsPolicyEnforcementV223):
|
|
|
|
wsgi_api_version = '2.24'
|
|
|
|
def test_migrate_delete_failed(self):
|
|
rule_name = "os_compute_api:servers:migrations:delete"
|
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
|
self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller.delete, self.req,
|
|
fakes.FAKE_UUID, '10')
|