diff --git a/nova/api/openstack/compute/migrate_server.py b/nova/api/openstack/compute/migrate_server.py index 8ee66c67d519..a251334fb906 100644 --- a/nova/api/openstack/compute/migrate_server.py +++ b/nova/api/openstack/compute/migrate_server.py @@ -26,6 +26,7 @@ from nova.api import validation from nova import compute from nova import exception from nova.i18n import _ +from nova import network from nova.policies import migrate_server as ms_policies LOG = logging.getLogger(__name__) @@ -35,6 +36,7 @@ class MigrateServerController(wsgi.Controller): def __init__(self, *args, **kwargs): super(MigrateServerController, self).__init__(*args, **kwargs) self.compute_api = compute.API() + self.network_api = network.API() @wsgi.response(202) @wsgi.expected_errors((400, 403, 404, 409)) @@ -51,6 +53,14 @@ class MigrateServerController(wsgi.Controller): host_name = body['migrate'].get('host') instance = common.get_instance(self.compute_api, context, id) + + if (common.instance_has_port_with_resource_request( + context, instance.uuid, self.network_api) and not + common.supports_port_resource_request_during_move(req)): + msg = _("The migrate server operation with port having QoS policy " + "is not supported.") + raise exc.HTTPBadRequest(explanation=msg) + try: self.compute_api.resize(req.environ['nova.context'], instance, host_name=host_name) @@ -106,6 +116,14 @@ class MigrateServerController(wsgi.Controller): # conductor instance = common.get_instance(self.compute_api, context, id, expected_attrs=['numa_topology']) + + if (common.instance_has_port_with_resource_request( + context, instance.uuid, self.network_api) and not + common.supports_port_resource_request_during_move(req)): + msg = _("The live migrate server operation with port having QoS " + "policy is not supported.") + raise exc.HTTPBadRequest(explanation=msg) + try: self.compute_api.live_migrate(context, instance, block_migration, disk_over_commit, host, force, diff --git a/nova/tests/functional/api_sample_tests/test_migrate_server.py b/nova/tests/functional/api_sample_tests/test_migrate_server.py index 7ae24c6afcf4..411c33c52738 100644 --- a/nova/tests/functional/api_sample_tests/test_migrate_server.py +++ b/nova/tests/functional/api_sample_tests/test_migrate_server.py @@ -23,6 +23,7 @@ from nova.tests.functional.api_sample_tests import test_servers class MigrateServerSamplesJsonTest(test_servers.ServersSampleBase): sample_dir = "os-migrate-server" + USE_NEUTRON = True def setUp(self): """setUp Method for MigrateServer api samples extension @@ -148,6 +149,7 @@ class MigrateServerSamplesJsonTestV256(test_servers.ServersSampleBase): sample_dir = "os-migrate-server" microversion = '2.56' scenarios = [('v2_56', {'api_major_version': 'v2.1'})] + USE_NEUTRON = True def setUp(self): """setUp Method for MigrateServer api samples extension diff --git a/nova/tests/functional/api_sample_tests/test_server_migrations.py b/nova/tests/functional/api_sample_tests/test_server_migrations.py index f4a40ada401c..b517c695eb7c 100644 --- a/nova/tests/functional/api_sample_tests/test_server_migrations.py +++ b/nova/tests/functional/api_sample_tests/test_server_migrations.py @@ -30,6 +30,7 @@ class ServerMigrationsSampleJsonTest(test_servers.ServersSampleBase): sample_dir = 'server-migrations' scenarios = [('v2_22', {'api_major_version': 'v2.1'})] microversion = '2.22' + USE_NEUTRON = True def setUp(self): """setUp method for server usage.""" @@ -167,6 +168,7 @@ class ServerMigrationsSampleJsonTestV2_24(test_servers.ServersSampleBase): microversion = '2.24' sample_dir = "server-migrations" scenarios = [('v2_24', {'api_major_version': 'v2.1'})] + USE_NEUTRON = True def setUp(self): """setUp method for server usage.""" @@ -233,6 +235,7 @@ class ServerMigrationsSampleJsonTestV2_65(ServerMigrationsSampleJsonTestV2_24): ADMIN_API = True microversion = '2.65' scenarios = [('v2_65', {'api_major_version': 'v2.1'})] + USE_NEUTRON = True @mock.patch.object(conductor_manager.ComputeTaskManager, '_live_migrate') def test_live_migrate_abort_migration_queued(self, _live_migrate): diff --git a/nova/tests/functional/compute/test_live_migration.py b/nova/tests/functional/compute/test_live_migration.py index a2ffbbab0117..fd44a710183b 100644 --- a/nova/tests/functional/compute/test_live_migration.py +++ b/nova/tests/functional/compute/test_live_migration.py @@ -45,6 +45,7 @@ class LiveMigrationCinderFailure(integrated_helpers._IntegratedTestBase, integrated_helpers.InstanceHelperMixin): api_major_version = 'v2.1' microversion = 'latest' + USE_NEUTRON = True def setUp(self): super(LiveMigrationCinderFailure, self).setUp() diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index a22ef8c4c365..a3ebdc58f2f2 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -5486,3 +5486,56 @@ class PortResourceRequestBasedSchedulingTest( self.assertIn( 'The resize server operation with port having QoS policy is not ' 'supported.', six.text_type(ex)) + + def test_migrate_server_with_port_resource_request_old_microversion(self): + server = self._create_server( + flavor=self.flavor, + networks=[{'port': self.neutron.port_1['id']}]) + self._wait_for_state_change(self.admin_api, server, 'ACTIVE') + + # We need to simulate that the above server has a port that has + # resource request, we cannot boot with such a port but legacy servers + # can exists with such a port. + bound_port = self.neutron._ports[self.neutron.port_1['id']] + fake_resource_request = self.neutron.port_with_resource_request[ + 'resource_request'] + bound_port['resource_request'] = fake_resource_request + + ex = self.assertRaises( + client.OpenStackApiException, + self.api.post_server_action, server['id'], {'migrate': None}) + + self.assertEqual(400, ex.response.status_code) + self.assertIn( + 'The migrate server operation with port having QoS policy is not ' + 'supported.', six.text_type(ex)) + + def test_live_migrate_server_with_port_resource_request_old_microversion( + self): + server = self._create_server( + flavor=self.flavor, + networks=[{'port': self.neutron.port_1['id']}]) + self._wait_for_state_change(self.admin_api, server, 'ACTIVE') + + # We need to simulate that the above server has a port that has + # resource request, we cannot boot with such a port but legacy servers + # can exists with such a port. + bound_port = self.neutron._ports[self.neutron.port_1['id']] + fake_resource_request = self.neutron.port_with_resource_request[ + 'resource_request'] + bound_port['resource_request'] = fake_resource_request + + post = { + 'os-migrateLive': { + 'host': None, + 'block_migration': False, + } + } + ex = self.assertRaises( + client.OpenStackApiException, + self.api.post_server_action, server['id'], post) + + self.assertEqual(400, ex.response.status_code) + self.assertIn( + 'The live migrate server operation with port having QoS policy is ' + 'not supported.', six.text_type(ex)) diff --git a/nova/tests/unit/api/openstack/compute/test_migrate_server.py b/nova/tests/unit/api/openstack/compute/test_migrate_server.py index 0ea4d092d97a..bd5b714d4add 100644 --- a/nova/tests/unit/api/openstack/compute/test_migrate_server.py +++ b/nova/tests/unit/api/openstack/compute/test_migrate_server.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import fixtures import mock from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import uuidutils @@ -46,6 +47,10 @@ class MigrateServerTestsV21(admin_only_action_common.CommonTests): self.stub_out('nova.api.openstack.compute.migrate_server.' 'MigrateServerController', lambda *a, **kw: self.controller) + self.mock_list_port = self.useFixture( + fixtures.MockPatch( + 'nova.network.neutronv2.api.API.list_ports')).mock + self.mock_list_port.return_value = {'ports': []} def _get_migration_body(self, **kwargs): return {'os-migrateLive': self._get_params(**kwargs)} @@ -101,6 +106,26 @@ class MigrateServerTestsV21(admin_only_action_common.CommonTests): args_map=args_map, method_translations=method_translations, exception_args=exception_arg) + def test_migrate_with_port_resource_request_old_microversion(self): + self.mock_list_port.return_value = {'ports': [ + {'resource_request': { + "resources": {'CUSTOM_FOO': 1}}}] + } + method_translations = {'_migrate': 'resize', + '_migrate_live': 'live_migrate'} + body_map = {'_migrate_live': self._get_migration_body(host='hostname')} + args_map = {'_migrate_live': ((False, self.disk_over_commit, + 'hostname', self.force, self.async_), + {}), + '_migrate': ((), {'host_name': self.host_name})} + ex = self.assertRaises( + webob.exc.HTTPBadRequest, self._test_actions, + ['_migrate', '_migrate_live'], body_map=body_map, + method_translations=method_translations, args_map=args_map) + self.assertIn( + 'The migrate server operation with port having QoS policy is not ' + 'supported.', six.text_type(ex)) + def test_actions_with_locked_instance(self): method_translations = {'_migrate': 'resize', '_migrate_live': 'live_migrate'} @@ -529,6 +554,20 @@ class MigrateServerTestsV256(MigrateServerTestsV234): method_translations=self.method_translations, exception_args=exception_arg) + def test_migrate_with_port_resource_request_old_microversion(self): + self.mock_list_port.return_value = {'ports': [ + {'resource_request': { + "resources": {'CUSTOM_FOO': 1}}}] + } + ex = self.assertRaises( + webob.exc.HTTPBadRequest, self._test_actions, + ['_migrate'], body_map=self.body_map, + method_translations=self.method_translations, + args_map=self.args_map) + self.assertIn( + 'The migrate server operation with port having QoS policy is not ' + 'supported.', six.text_type(ex)) + def test_actions_with_locked_instance(self): self._test_actions_with_locked_instance( ['_migrate'], body_map=self.body_map,