diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 171baab5d6ff..f9a8966b201d 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -767,7 +767,8 @@ class ServersController(wsgi.Controller): except (exception.PortInUse, exception.InstanceExists, exception.NetworkAmbiguous, - exception.NoUniqueMatch) as error: + exception.NoUniqueMatch, + exception.MixedInstanceNotSupportByComputeService) as error: raise exc.HTTPConflict(explanation=error.format_message()) # If the caller wanted a reservation_id, return it @@ -960,7 +961,8 @@ class ServersController(wsgi.Controller): explanation=error.format_message()) except (exception.InstanceIsLocked, exception.InstanceNotReady, - exception.ServiceUnavailable) as e: + exception.ServiceUnavailable, + exception.MixedInstanceNotSupportByComputeService) as e: raise exc.HTTPConflict(explanation=e.format_message()) except exception.InstanceInvalidState as state_error: common.raise_http_conflict_for_instance_invalid_state(state_error, diff --git a/nova/compute/api.py b/nova/compute/api.py index 1feb649cba85..9c6747d5d8f2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -106,6 +106,9 @@ MIN_COMPUTE_SYNC_COMPUTE_STATUS_DISABLED = 38 MIN_COMPUTE_CROSS_CELL_RESIZE = 47 MIN_COMPUTE_SAME_HOST_COLD_MIGRATE = 48 +# TODO(huaqiang): Remove in Wallaby +MIN_VER_NOVA_COMPUTE_MIXED_POLICY = 52 + # FIXME(danms): Keep a global cache of the cells we find the # first time we look. This needs to be refreshed on a timer or # trigger. @@ -711,6 +714,28 @@ class API(base.Base): image, instance_type, validate_numa=validate_numa, validate_pci=validate_pci) + # TODO(huaqiang): Remove in Wallaby when there is no nova-compute node + # having a version prior to Victoria. + @staticmethod + def _check_compute_service_for_mixed_instance(numa_topology): + """Check if the nova-compute service is ready to support mixed instance + when the CPU allocation policy is 'mixed'. + """ + # No need to check the instance with no NUMA topology associated with. + if numa_topology is None: + return + + # No need to check if instance CPU policy is not 'mixed' + if numa_topology.cpu_policy != fields_obj.CPUAllocationPolicy.MIXED: + return + + # Catch a request creating a mixed instance, make sure all nova-compute + # service have been upgraded and support the mixed policy. + minimal_version = objects.service.get_minimum_version_all_cells( + nova_context.get_admin_context(), ['nova-compute']) + if minimal_version < MIN_VER_NOVA_COMPUTE_MIXED_POLICY: + raise exception.MixedInstanceNotSupportByComputeService() + @staticmethod def _validate_flavor_image_numa_pci(image, instance_type, validate_numa=True, @@ -1449,6 +1474,12 @@ class API(base.Base): requested_networks, config_drive, auto_disk_config, reservation_id, max_count, supports_port_resource_request) + # TODO(huaqiang): Remove in Wallaby + # check nova-compute nodes have been updated to Victoria to support the + # mixed CPU policy for creating a new instance. + numa_topology = base_options.get('numa_topology') + self._check_compute_service_for_mixed_instance(numa_topology) + # max_net_count is the maximum number of instances requested by the # user adjusted for any network quota constraints, including # consideration of connections to each requested network @@ -3917,6 +3948,12 @@ class API(base.Base): if not same_instance_type: request_spec.numa_topology = hardware.numa_get_constraints( new_instance_type, instance.image_meta) + # TODO(huaqiang): Remove in Wallaby + # check nova-compute nodes have been updated to Victoria to resize + # instance to a new mixed instance from a dedicated or shared + # instance. + self._check_compute_service_for_mixed_instance( + request_spec.numa_topology) instance.task_state = task_states.RESIZE_PREP instance.progress = 0 diff --git a/nova/exception.py b/nova/exception.py index e5dd12a6b87e..8f6ec42a63af 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2325,3 +2325,8 @@ class RequiredMixedOrRealtimeCPUMask(Invalid): msg_fmt = _("Must specify either 'hw:cpu_dedicated_mask' or " "'hw:cpu_realtime_mask' when using 'mixed' CPU policy" " instance.") + + +class MixedInstanceNotSupportByComputeService(NovaException): + msg_fmt = _("To support 'mixed' policy instance 'nova-compute' service " + "must be upgraded to 'Victoria' or later.") diff --git a/nova/objects/service.py b/nova/objects/service.py index 887566266836..82203e1246a4 100644 --- a/nova/objects/service.py +++ b/nova/objects/service.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) # NOTE(danms): This is the global service version counter -SERVICE_VERSION = 51 +SERVICE_VERSION = 52 # NOTE(danms): This is our SERVICE_VERSION history. The idea is that any @@ -185,6 +185,8 @@ SERVICE_VERSION_HISTORY = ( {'compute_rpc': '5.11'}, # Version 51: Add support for live migration with vpmem {'compute_rpc': '5.11'}, + # Version 52: Add support for the 'mixed' CPU allocation policy + {'compute_rpc': '5.11'}, ) diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py index 2416f454a019..5936a55f36c2 100644 --- a/nova/tests/unit/compute/test_compute_api.py +++ b/nova/tests/unit/compute/test_compute_api.py @@ -360,6 +360,34 @@ class _ComputeAPIUnitTestMixIn(object): self._test_specified_ip_and_multiple_instances_helper( requested_networks) + # TODO(huaqiang): Remove in Wallaby + # TODO(huaqiang): To be removed when 'hw:cpu_dedicated_mask' could be + # parsed from flavor extra spec. + @mock.patch('nova.virt.hardware.get_dedicated_cpu_constraint', + mock.Mock(return_value=set([0, 1, 2]))) + @mock.patch('nova.compute.api.API._check_requested_networks', + new=mock.Mock(return_value=1)) + @mock.patch('nova.virt.hardware.get_pci_numa_policy_constraint', + return_value=None) + def test_create_mixed_instance_compute_version_fail(self, mock_pci): + """Ensure a 'MixedInstanceNotSupportByComputeService' exception raises + when all 'nova-compute' nodes have not been upgraded to or after + Victoria. + """ + extra_specs = { + "hw:cpu_policy": "mixed", + "hw:cpu_dedicated_mask": "^3" + } + flavor = self._create_flavor(vcpus=4, extra_specs=extra_specs) + + self.assertRaises(exception.MixedInstanceNotSupportByComputeService, + self.compute_api.create, self.context, flavor, + image_href=None) + # Ensure the exception is raised right after the call of + # '_validate_and_build_base_options's since + # 'get_pci_numa_policy_constraint' is only called in this method. + mock_pci.assert_called_once() + @mock.patch('nova.objects.BlockDeviceMapping.save') @mock.patch.object(compute_rpcapi.ComputeAPI, 'reserve_block_device_name') def test_create_volume_bdm_call_reserve_dev_name(self, mock_reserve, @@ -2336,6 +2364,56 @@ class _ComputeAPIUnitTestMixIn(object): else: self.fail("Exception not raised") + # TODO(huaqiang): Remove in Wallaby + # TODO(huaqiang): To be removed when 'hw:cpu_dedicated_mask' could be + # parsed from flavor extra spec. + @mock.patch('nova.virt.hardware.get_dedicated_cpu_constraint', + mock.Mock(return_value=set([3]))) + @mock.patch('nova.compute.api.API.get_instance_host_status', + new=mock.Mock(return_value=fields_obj.HostStatus.UP)) + @mock.patch.object(compute_utils, 'is_volume_backed_instance', + new=mock.Mock(return_value=True)) + @mock.patch('nova.objects.service.get_minimum_version_all_cells', + new=mock.Mock(return_value=51)) + @mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid') + @mock.patch.object(utils, 'get_image_from_system_metadata') + @mock.patch.object(flavors, 'get_flavor_by_flavor_id') + def test_resize_mixed_instance_compute_version_low_fails( + self, mock_get_flavor, mock_image, mock_spec): + """Check resizing an mixed policy instance fails if some + nova-compute node is not upgraded to Victory yet. + """ + numa_topology = objects.InstanceNUMATopology(cells=[ + objects.InstanceNUMACell( + id=0, cpuset=set(), pcpuset=set([0, 1, 2, 3]), memory=1024, + pagesize=None, cpu_pinning_raw=None, cpuset_reserved=None, + cpu_policy='dedicated', cpu_thread_policy=None) + ]) + flavor = objects.Flavor(id=1, name='current', vcpus=4, memory_mb=1024, + root_gb=10, disabled=False, + extra_specs={ + "hw:cpu_policy": "dedicated" + }) + new_flavor = objects.Flavor(id=2, name='new', vcpus=4, memory_mb=1024, + root_gb=10, disabled=False, + extra_specs={ + "hw:cpu_policy": "mixed", + "hw:cpu_dedicated_mask": "^0-2", + }) + image = {"properties": {}} + params = { + 'vcpus': 4, + 'numa_topology': numa_topology, + } + + mock_image.return_value = image + mock_get_flavor.return_value = new_flavor + instance = self._create_instance_obj(params=params, flavor=flavor) + self.assertRaises(exception.MixedInstanceNotSupportByComputeService, + self.compute_api.resize, self.context, + instance, flavor_id='flavor-new') + mock_spec.assert_called_once() + @mock.patch.object(compute_api.API, '_record_action_start') @mock.patch.object(objects.Instance, 'save') def test_pause(self, mock_save, mock_record): @@ -7410,6 +7488,29 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase): # myfunc was not called self.assertEqual({}, args_info) + # TODO(huaqiang): Remove in Wallaby + @mock.patch('nova.objects.service.get_minimum_version_all_cells') + def test__check_compute_service_for_mixed_instance(self, mock_ver): + """Ensure a 'MixedInstanceNotSupportByComputeService' exception raises + if any compute node has not been upgraded to Victoria or later. + """ + mock_ver.side_effect = [52, 51] + fake_numa_topo = objects.InstanceNUMATopology(cells=[ + objects.InstanceNUMACell( + id=0, cpuset=set([0]), pcpuset=set([1]), memory=512, + cpu_policy='mixed') + ]) + + self.compute_api._check_compute_service_for_mixed_instance( + fake_numa_topo) + # 'get_minimum_version_all_cells' has been called + mock_ver.assert_called() + + self.assertRaises( + exception.MixedInstanceNotSupportByComputeService, + self.compute_api._check_compute_service_for_mixed_instance, + fake_numa_topo) + class DiffDictTestCase(test.NoDBTestCase): """Unit tests for _diff_dict()."""