From bba739a6e6c8c95fae7a788d19ecc1458ce016c9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 27 Jul 2015 10:46:37 +0100 Subject: [PATCH] Add 'cpu_policy' and 'cpu_thread_policy' fields These fields can be used to store the values for an instance's CPU policy-related metadata. The former is helpful to clarify exactly what CPU policy is applied to the instance and will allow for the removal of some "magic" currently used to detect this. The latter is required by the scheduler to decide which CPU threads policy to apply. Change-Id: I5095fc7703e0c7dcdab120dfdd35e3d8322d270f Implements: blueprint virt-driver-cpu-thread-pinning Co-Authored-By: Przemyslaw Czesnowicz --- nova/exception.py | 10 ++++++++ nova/objects/instance_numa_topology.py | 25 ++++++++++++++++--- nova/tests/unit/objects/test_objects.py | 2 +- nova/tests/unit/virt/test_hardware.py | 32 +++++++++++++++++++++++++ nova/virt/hardware.py | 23 ++++++++++++++++-- 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/nova/exception.py b/nova/exception.py index 6507749da9..feb3532ce3 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)