diff --git a/nova/exception.py b/nova/exception.py index 62b87e03db..f5ac74708c 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1974,6 +1974,11 @@ class ImageCPUPinningForbidden(Forbidden): "CPU pinning policy set against the flavor") +class ImageCPUThreadPolicyForbidden(Forbidden): + msg_fmt = _("Image property 'hw_cpu_thread_policy' is not permitted to " + "override CPU thread pinning policy set against the flavor") + + class UnsupportedPolicyException(Invalid): msg_fmt = _("ServerGroup policy is not supported: %(reason)s") @@ -2015,6 +2020,11 @@ class RealtimeConfigurationInvalid(Invalid): "cpu pinning policy") +class CPUThreadPolicyConfigurationInvalid(Invalid): + msg_fmt = _("Cannot set cpu thread pinning policy in a non dedicated " + "cpu pinning policy") + + class RequestSpecNotFound(NotFound): msg_fmt = _("RequestSpec not found for instance %(instance_uuid)s") diff --git a/nova/objects/instance_numa_topology.py b/nova/objects/instance_numa_topology.py index da8d4c8dfe..f6f122217c 100644 --- a/nova/objects/instance_numa_topology.py +++ b/nova/objects/instance_numa_topology.py @@ -13,6 +13,7 @@ # under the License. from oslo_serialization import jsonutils +from oslo_utils import versionutils from nova import db from nova import exception @@ -28,7 +29,16 @@ class InstanceNUMACell(base.NovaObject, # Version 1.0: Initial version # Version 1.1: Add pagesize field # Version 1.2: Add cpu_pinning_raw and topology fields - VERSION = '1.2' + # Version 1.3: Add cpu_policy and cpu_thread_policy fields + VERSION = '1.3' + + def obj_make_compatible(self, primitive, target_version): + super(InstanceNUMACell, self).obj_make_compatible(primitive, + target_version) + target_version = versionutils.convert_version_to_tuple(target_version) + if target_version < (1, 3): + primitive.pop('cpu_policy', None) + primitive.pop('cpu_thread_policy', None) fields = { 'id': obj_fields.IntegerField(), @@ -37,8 +47,11 @@ class InstanceNUMACell(base.NovaObject, 'pagesize': obj_fields.IntegerField(nullable=True), 'cpu_topology': obj_fields.ObjectField('VirtCPUTopology', nullable=True), - 'cpu_pinning_raw': obj_fields.DictOfIntegersField(nullable=True) - } + 'cpu_pinning_raw': obj_fields.DictOfIntegersField(nullable=True), + 'cpu_policy': obj_fields.CPUAllocationPolicyField(nullable=True), + 'cpu_thread_policy': obj_fields.CPUThreadAllocationPolicyField( + nullable=True), + } cpu_pinning = obj_fields.DictProxyField('cpu_pinning_raw') @@ -53,6 +66,12 @@ class InstanceNUMACell(base.NovaObject, if 'cpu_pinning' not in kwargs: self.cpu_pinning = None self.obj_reset_changes(['cpu_pinning_raw']) + if 'cpu_policy' not in kwargs: + self.cpu_policy = None + self.obj_reset_changes(['cpu_policy']) + if 'cpu_thread_policy' not in kwargs: + self.cpu_thread_policy = None + self.obj_reset_changes(['cpu_thread_policy']) def __len__(self): return len(self.cpuset) diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index dcf13293f9..c3f9168fd0 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1148,7 +1148,7 @@ object_data = { 'InstanceList': '2.0-6c8ba6147cca3082b1e4643f795068bf', 'InstanceMapping': '1.0-47ef26034dfcbea78427565d9177fe50', 'InstanceMappingList': '1.0-9e982e3de1613b9ada85e35f69b23d47', - 'InstanceNUMACell': '1.2-535ef30e0de2d6a0d26a71bd58ecafc4', + 'InstanceNUMACell': '1.3-6991a20992c5faa57fae71a45b40241b', 'InstanceNUMATopology': '1.2-d944a7d6c21e1c773ffdf09c6d025954', 'InstancePCIRequest': '1.1-b1d75ebc716cb12906d9d513890092bf', 'InstancePCIRequests': '1.1-65e38083177726d806684cb1cc0136d2', diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py index 3816cb73a0..9a7d35dd3a 100644 --- a/nova/tests/unit/virt/test_hardware.py +++ b/nova/tests/unit/virt/test_hardware.py @@ -23,6 +23,7 @@ from nova import context from nova import exception from nova import objects from nova.objects import base as base_obj +from nova.objects import fields from nova.pci import stats from nova import test from nova.virt import hardware as hw @@ -1103,6 +1104,37 @@ class NUMATopologyTest(test.NoDBTestCase): }, "expect": exception.RealtimeConfigurationInvalid, }, + { + # Invalid CPU thread pinning override + "flavor": objects.Flavor(vcpus=4, memory_mb=2048, + extra_specs={ + "hw:numa_nodes": 2, "hw:cpu_policy": "dedicated", + "hw:cpu_thread_policy": + fields.CPUThreadAllocationPolicy.ISOLATE, + }), + "image": { + "properties": { + "hw_cpu_policy": "dedicated", + "hw_cpu_thread_policy": + fields.CPUThreadAllocationPolicy.REQUIRE, + } + }, + "expect": exception.ImageCPUThreadPolicyForbidden, + }, + { + # Invalid CPU pinning policy with CPU thread pinning + "flavor": objects.Flavor(vcpus=4, memory_mb=2048, + extra_specs={ + "hw:cpu_policy": "shared", + "hw:cpu_thread_policy": + fields.CPUThreadAllocationPolicy.ISOLATE, + }), + "image": { + "properties": {} + }, + "expect": exception.CPUThreadPolicyConfigurationInvalid, + }, + ] for testitem in testdata: diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index e3d7345fe2..c0f95f0848 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -27,6 +27,7 @@ from nova import context from nova import exception from nova.i18n import _ from nova import objects +from nova.objects import fields from nova.objects import instance as obj_instance @@ -1018,21 +1019,38 @@ def _add_cpu_pinning_constraint(flavor, image_meta, numa_topology): if rt and pi != "dedicated": raise exception.RealtimeConfigurationInvalid() + flavor_thread_policy = flavor.get('extra_specs', {}).get( + 'hw:cpu_thread_policy') + image_thread_policy = image_meta.properties.get('hw_cpu_thread_policy') + if not requested: + if flavor_thread_policy or image_thread_policy: + raise exception.CPUThreadPolicyConfigurationInvalid() return numa_topology + if flavor_thread_policy in [None, fields.CPUThreadAllocationPolicy.PREFER]: + cpu_thread_policy = image_thread_policy + elif image_thread_policy and image_thread_policy != flavor_thread_policy: + raise exception.ImageCPUThreadPolicyForbidden() + else: + cpu_thread_policy = flavor_thread_policy + if numa_topology: # NOTE(ndipanov) Setting the cpu_pinning attribute to a non-None value # means CPU pinning was requested + # TODO(sfinucan) Instead of using the "magic" described above, make use + # of the 'InstanceNUMACell.cpu_policy' parameter for cell in numa_topology.cells: cell.cpu_pinning = {} + cell.cpu_thread_policy = cpu_thread_policy return numa_topology else: single_cell = objects.InstanceNUMACell( id=0, cpuset=set(range(flavor.vcpus)), memory=flavor.memory_mb, - cpu_pinning={}) + cpu_pinning={}, + cpu_thread_policy=cpu_thread_policy) numa_topology = objects.InstanceNUMATopology(cells=[single_cell]) return numa_topology @@ -1254,7 +1272,8 @@ def instance_topology_from_instance(instance): cpuset=set(cell['cpuset']), memory=cell['memory'], pagesize=cell.get('pagesize'), - cpu_pinning=cell.get('cpu_pinning_raw')) + cpu_pinning=cell.get('cpu_pinning_raw'), + cpu_thread_policy=cell.get('cpu_thread_policy')) for cell in dict_cells] instance_numa_topology = objects.InstanceNUMATopology( cells=cells)