Files
nova/nova/tests/functional/api_sample_tests/test_server_migrations.py
T
Andrea Rosa fa00292546 Abort an ongoing live migration
This change adds a DELETE call on the server-migrations object to cancel
a running live migration of a specific instance.
TO perform the cancellation the virtualization driver needs to support
it, in case that the feature is not supported we return an error.
We allow a cancellation of a migration only if the migration is
running at the moment of the request and if the migration type is equal
to 'live-migration'.
In this change we implement this feature for the libvirt driver.
When the cancellation of a live migration succeeded we rollback the live
migration and we set the state of the Migration object equals to
'cancelled'.
The implementation of this change is based on the work done by the
implementation of the feature called 'force live migration':
https://review.openstack.org/245921

DocImpact
ApiImpact

Implements blueprint: abort-live-migration
Change-Id: I1ff861e54997a069894b542bd764ac3ef1b3dbb2
2016-02-26 15:11:41 +00:00

212 lines
8.5 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 datetime
import mock
from nova.conductor import manager as conductor_manager
from nova import context
from nova import db
from nova import objects
from nova.tests.functional.api_sample_tests import test_servers
from nova.tests.unit import fake_instance
class ServerMigrationsSampleJsonTest(test_servers.ServersSampleBase):
extension_name = 'server-migrations'
scenarios = [('v2_22', {'api_major_version': 'v2.1'})]
extra_extensions_to_load = ["os-migrate-server", "os-access-ips"]
def setUp(self):
"""setUp method for server usage."""
super(ServerMigrationsSampleJsonTest, self).setUp()
self.uuid = self._post_server()
self.api.microversion = '2.22'
@mock.patch.object(conductor_manager.ComputeTaskManager, '_live_migrate')
@mock.patch.object(db, 'service_get_by_compute_host')
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
@mock.patch('nova.compute.manager.ComputeManager.'
'live_migration_force_complete')
def test_live_migrate_force_complete(self, live_migration_pause_instance,
get_by_id_and_instance,
service_get_by_compute_host,
_live_migrate):
migration = objects.Migration()
migration.id = 1
migration.status = 'running'
get_by_id_and_instance.return_value = migration
self._do_post('servers/%s/action' % self.uuid, 'live-migrate-server',
{'hostname': self.compute.host})
response = self._do_post('servers/%s/migrations/%s/action'
% (self.uuid, '3'), 'force_complete', {})
self.assertEqual(202, response.status_code)
def test_get_migration(self):
response = self._do_get('servers/fake_id/migrations/1234')
self.assertEqual(404, response.status_code)
def test_list_migrations(self):
response = self._do_get('servers/fake_id/migrations')
self.assertEqual(404, response.status_code)
class ServerMigrationsSamplesJsonTestV2_23(test_servers.ServersSampleBase):
ADMIN_API = True
extension_name = "server-migrations"
microversion = '2.23'
scenarios = [('v2_23', {'api_major_version': 'v2.1'})]
UUID_1 = '4cfba335-03d8-49b2-8c52-e69043d1e8fe'
UUID_2 = '058fc419-a8a8-4e08-b62c-a9841ef9cd3f'
fake_migrations = [
{
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'running',
'instance_uuid': UUID_1,
'migration_type': 'live-migration',
'hidden': False,
'memory_total': 123456,
'memory_processed': 12345,
'memory_remaining': 120000,
'disk_total': 234567,
'disk_processed': 23456,
'disk_remaining': 230000,
'created_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 29, 13, 42, 2),
'deleted_at': None,
'deleted': False
},
{
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'migrating',
'instance_uuid': UUID_2,
'migration_type': 'resize',
'hidden': False,
'memory_total': 456789,
'memory_processed': 56789,
'memory_remaining': 45000,
'disk_total': 96789,
'disk_processed': 6789,
'disk_remaining': 96000,
'created_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'updated_at': datetime.datetime(2016, 0o1, 22, 13, 42, 2),
'deleted_at': None,
'deleted': False
}
]
def setUp(self):
super(ServerMigrationsSamplesJsonTestV2_23, self).setUp()
fake_context = context.RequestContext('fake', 'fake')
self.mig1 = objects.Migration(
context=fake_context, **self.fake_migrations[0])
self.mig1.create()
self.mig2 = objects.Migration(
context=fake_context, **self.fake_migrations[1])
self.mig2.create()
fake_ins = fake_instance.fake_db_instance(uuid=self.UUID_1)
fake_ins.pop("pci_devices")
fake_ins.pop("security_groups")
fake_ins.pop("services")
fake_ins.pop("tags")
fake_ins.pop("info_cache")
fake_ins.pop("id")
self.instance = objects.Instance(
context=fake_context,
**fake_ins)
self.instance.create()
def test_get_migration(self):
response = self._do_get('servers/%s/migrations/%s' %
(self.fake_migrations[0]["instance_uuid"],
self.mig1.id))
self.assertEqual(200, response.status_code)
self._verify_response('migrations-get',
{"server_uuid": self.UUID_1},
response, 200)
def test_list_migrations(self):
response = self._do_get('servers/%s/migrations' %
self.fake_migrations[0]["instance_uuid"])
self.assertEqual(200, response.status_code)
self._verify_response('migrations-index',
{"server_uuid_1": self.UUID_1},
response, 200)
class ServerMigrationsSampleJsonTestV2_24(test_servers.ServersSampleBase):
ADMIN_API = True
extension_name = "server-migrations"
scenarios = [('v2_24', {'api_major_version': 'v2.1'})]
extra_extensions_to_load = ["os-migrate-server", "os-access-ips"]
def setUp(self):
"""setUp method for server usage."""
super(ServerMigrationsSampleJsonTestV2_24, self).setUp()
self.uuid = self._post_server()
self.context = context.RequestContext('fake', 'fake')
fake_migration = {
'source_node': self.compute.host,
'dest_node': 'node10',
'source_compute': 'compute1',
'dest_compute': 'compute12',
'migration_type': 'live-migration',
'instance_uuid': self.uuid,
'status': 'running'}
self.migration = objects.Migration(context=self.context,
**fake_migration)
self.migration.create()
@mock.patch.object(conductor_manager.ComputeTaskManager, '_live_migrate')
def test_live_migrate_abort(self, _live_migrate):
self._do_post('servers/%s/action' % self.uuid, 'live-migrate-server',
{'hostname': self.compute.host})
uri = 'servers/%s/migrations/%s' % (self.uuid, self.migration.id)
response = self._do_delete(uri, api_version='2.24')
self.assertEqual(202, response.status_code)
@mock.patch.object(conductor_manager.ComputeTaskManager, '_live_migrate')
def test_live_migrate_abort_migration_not_found(self, _live_migrate):
self._do_post('servers/%s/action' % self.uuid, 'live-migrate-server',
{'hostname': self.compute.host})
uri = 'servers/%s/migrations/%s' % (self.uuid, '45')
response = self._do_delete(uri, api_version='2.24')
self.assertEqual(404, response.status_code)
@mock.patch.object(conductor_manager.ComputeTaskManager, '_live_migrate')
def test_live_migrate_abort_migration_not_running(self, _live_migrate):
self.migration.status = 'completed'
self.migration.save()
self._do_post('servers/%s/action' % self.uuid, 'live-migrate-server',
{'hostname': self.compute.host})
uri = 'servers/%s/migrations/%s' % (self.uuid, self.migration.id)
response = self._do_delete(uri, api_version='2.24')
self.assertEqual(400, response.status_code)