Merge "objects: Introduce the 'CPUAllocationPolicy.MIXED' enum"

This commit is contained in:
Zuul 2020-07-16 11:06:21 +00:00 committed by Gerrit Code Review
commit c1a4828f6e
17 changed files with 203 additions and 20 deletions

View File

@ -4,5 +4,5 @@
"hw_architecture": "x86_64"
},
"nova_object.name": "ImageMetaPropsPayload",
"nova_object.version": "1.3"
"nova_object.version": "1.4"
}

View File

@ -1,5 +1,5 @@
{
"nova_object.version": "1.1",
"nova_object.version": "1.2",
"nova_object.namespace": "nova",
"nova_object.name": "InstanceNUMACellPayload",
"nova_object.data": {

View File

@ -87,6 +87,8 @@ INVALID_FLAVOR_IMAGE_EXCEPTIONS = (
exception.PciRequestAliasNotDefined,
exception.RealtimeConfigurationInvalid,
exception.RealtimeMaskNotFoundOrInvalid,
exception.RequiredMixedInstancePolicy,
exception.RequiredMixedOrRealtimeCPUMask,
)
MIN_COMPUTE_MOVE_BANDWIDTH = 39

View File

@ -63,14 +63,17 @@ cpu_policy_validators = [
'CPUs can run on. If ``shared`` (default), guest CPUs can be '
'overallocated but cannot float across host cores. If '
'``dedicated``, guest CPUs cannot be overallocated but are '
'individually pinned to their own host core.'
'individually pinned to their own host core. ``mixed`` is a '
'policy with which the guest is mixing the overallocated and '
'pinned guest CPUs.'
),
value={
'type': str,
'description': 'The CPU policy.',
'enum': [
'dedicated',
'shared'
'shared',
'mixed',
],
},
),

View File

@ -2318,3 +2318,14 @@ class AcceleratorRequestOpFailed(NovaException):
class InvalidLibvirtGPUConfig(NovaException):
msg_fmt = _('Invalid configuration for GPU devices: %(reason)s')
class RequiredMixedInstancePolicy(Invalid):
msg_fmt = _("Cannot specify 'hw:cpu_dedicated_mask' without the "
"'mixed' policy.")
class RequiredMixedOrRealtimeCPUMask(Invalid):
msg_fmt = _("Must specify either 'hw:cpu_dedicated_mask' or "
"'hw:cpu_realtime_mask' when using 'mixed' CPU policy"
" instance.")

View File

@ -120,7 +120,8 @@ class ImageMetaPropsPayload(base.NotificationPayloadBase):
# Version 1.1: Added 'gop', 'virtio' and 'none' to hw_video_model field
# Version 1.2: Added hw_pci_numa_affinity_policy field
# Version 1.3: Added hw_mem_encryption, hw_pmu and hw_time_hpet fields
VERSION = '1.3'
# Version 1.4: Added 'mixed' to hw_cpu_policy field
VERSION = '1.4'
SCHEMA = {
k: ('image_meta_props', k) for k in image_meta.ImageMetaProps.fields}

View File

@ -144,7 +144,8 @@ class InstanceNUMATopologyPayload(base.NotificationPayloadBase):
class InstanceNUMACellPayload(base.NotificationPayloadBase):
# Version 1.0: Initial version
# Version 1.1: Added pcpuset field
VERSION = '1.1'
# Version 1.2: Added 'mixed' to cpu_policy field
VERSION = '1.2'
SCHEMA = {
'id': ('numa_cell', 'id'),

View File

@ -272,8 +272,9 @@ class CPUAllocationPolicy(BaseNovaEnum):
DEDICATED = "dedicated"
SHARED = "shared"
MIXED = "mixed"
ALL = (DEDICATED, SHARED)
ALL = (DEDICATED, SHARED, MIXED)
class CPUThreadAllocationPolicy(BaseNovaEnum):

View File

@ -175,14 +175,22 @@ class ImageMetaProps(base.NovaObject):
# Version 1.23: Added 'hw_pmu' field
# Version 1.24: Added 'hw_mem_encryption' field
# Version 1.25: Added 'hw_pci_numa_affinity_policy' field
# Version 1.26: Added 'mixed' to 'hw_cpu_policy' field
# NOTE(efried): When bumping this version, the version of
# ImageMetaPropsPayload must also be bumped. See its docstring for details.
VERSION = '1.25'
VERSION = '1.26'
def obj_make_compatible(self, primitive, target_version):
super(ImageMetaProps, self).obj_make_compatible(primitive,
target_version)
target_version = versionutils.convert_version_to_tuple(target_version)
if target_version < (1, 26):
policy = primitive.get('hw_cpu_policy', None)
if policy == fields.CPUAllocationPolicy.MIXED:
raise exception.ObjectActionError(
action='obj_make_compatible',
reason='hw_cpu_policy=%s not supported in version %s' %
(policy, target_version))
if target_version < (1, 25):
primitive.pop('hw_pci_numa_affinity_policy', None)
if target_version < (1, 24):

View File

@ -19,6 +19,7 @@ from oslo_utils import versionutils
from nova.db import api as db
from nova import exception
from nova.i18n import _
from nova.objects import base
from nova.objects import fields as obj_fields
from nova.virt import hardware
@ -34,12 +35,22 @@ class InstanceNUMACell(base.NovaEphemeralObject,
# Version 1.3: Add cpu_policy and cpu_thread_policy fields
# Version 1.4: Add cpuset_reserved field
# Version 1.5: Add pcpuset field
VERSION = '1.5'
# Version 1.6: Add 'mixed' to cpu_policy field
VERSION = '1.6'
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)
# Instance with a 'mixed' CPU policy could not provide a backward
# compatibility.
if target_version < (1, 6):
if primitive['cpu_policy'] == obj_fields.CPUAllocationPolicy.MIXED:
raise exception.ObjectActionError(
action='obj_make_compatible',
reason=_('mixed instance is not supported in version %s') %
target_version)
# NOTE(huaqiang): Since version 1.5, 'cpuset' is modified to track the
# unpinned CPUs only, with pinned CPUs tracked via 'pcpuset' instead.
# For a backward compatibility, move the 'dedicated' instance CPU list

View File

@ -1249,7 +1249,7 @@ class TestInstanceNotificationSample(
'nova_object.data': {},
'nova_object.name': 'ImageMetaPropsPayload',
'nova_object.namespace': 'nova',
'nova_object.version': u'1.3'},
'nova_object.version': u'1.4'},
'image.size': 58145823,
'image.tags': [],
'scheduler_hints': {'_nova_check_type': ['rebuild']},
@ -1344,7 +1344,7 @@ class TestInstanceNotificationSample(
'nova_object.data': {},
'nova_object.name': 'ImageMetaPropsPayload',
'nova_object.namespace': 'nova',
'nova_object.version': u'1.3'},
'nova_object.version': u'1.4'},
'image.size': 58145823,
'image.tags': [],
'scheduler_hints': {'_nova_check_type': ['rebuild']},

View File

@ -69,6 +69,7 @@ class TestValidators(test.NoDBTestCase):
('hw:cpu_thread_policy', 'prefer'),
('hw:emulator_threads_policy', 'isolate'),
('hw:pci_numa_affinity_policy', 'legacy'),
('hw:cpu_policy', 'mixed'),
)
for key, value in valid_specs:
validators.validate(key, value)

View File

@ -13474,10 +13474,17 @@ class CheckRequestedImageTestCase(test.TestCase):
self.context, image['id'],
image, self.instance_type, root_bdm)
def test_cpu_policy(self):
@mock.patch('nova.virt.hardware.get_dedicated_cpu_constraint')
def test_cpu_policy(self, dedicated_cpu_mock):
image = {'id': uuids.image_id, 'status': 'active'}
for v in obj_fields.CPUAllocationPolicy.ALL:
image['properties'] = {'hw_cpu_policy': v}
# 'mixed' policy requires a definition of 'cpu_dedicated_mask'
if v == obj_fields.CPUAllocationPolicy.MIXED:
dedicated_cpu_mock.return_value = set([0])
else:
dedicated_cpu_mock.return_value = None
self.compute_api._validate_flavor_image(
self.context, image['id'], image, self.instance_type, None)
image['properties'] = {'hw_cpu_policy': 'bar'}

View File

@ -387,7 +387,7 @@ notification_object_data = {
# ImageMetaProps, so when you see a fail here for that reason, you must
# *also* bump the version of ImageMetaPropsPayload. See its docstring for
# more information.
'ImageMetaPropsPayload': '1.3-9c200c895932163a4e14e6bb385fa1e0',
'ImageMetaPropsPayload': '1.4-036c794843b95a3a39ee70830f5f6557',
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'InstanceActionPayload': '1.8-4fa3da9cbf0761f1f700ae578f36dc2f',
'InstanceActionRebuildNotification':
@ -411,7 +411,7 @@ notification_object_data = {
'InstanceActionSnapshotPayload': '1.9-c3e0bbaaefafdfa2f8e6e504c2c9b12c',
'InstanceExistsNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'InstanceExistsPayload': '1.2-e082c02438ee57164829afaeee3bf7f8',
'InstanceNUMACellPayload': '1.1-2a24ab42bf5e8dfa98291402725bf278',
'InstanceNUMACellPayload': '1.2-a367add3378c71c21c817ab2b23db3bf',
'InstanceNUMATopologyPayload': '1.0-247361b152047c18ae9ad1da2544a3c9',
'InstancePCIRequestPayload': '1.0-12d0d61baf183daaafd93cbeeed2956f',
'InstancePCIRequestsPayload': '1.0-6751cffe0c0fabd212aad624f672429a',

View File

@ -1077,7 +1077,7 @@ object_data = {
'HyperVLiveMigrateData': '1.4-e265780e6acfa631476c8170e8d6fce0',
'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502',
'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d',
'ImageMetaProps': '1.25-66fc973af215eb5701ed4034bb6f0685',
'ImageMetaProps': '1.26-b9f136cd10a2b5ffb3ae44332f2f687d',
'Instance': '2.7-d187aec68cad2e4d8b8a03a68e4739ce',
'InstanceAction': '1.2-9a5abc87fdd3af46f45731960651efb5',
'InstanceActionEvent': '1.4-5b1f361bd81989f8bb2c20bb7e8a4cb4',
@ -1093,7 +1093,7 @@ object_data = {
'InstanceList': '2.6-238f125650c25d6d12722340d726f723',
'InstanceMapping': '1.2-3bd375e65c8eb9c45498d2f87b882e03',
'InstanceMappingList': '1.3-d34b6ebb076d542ae0f8b440534118da',
'InstanceNUMACell': '1.5-d6f884326eba8cae60930e06047fc7d9',
'InstanceNUMACell': '1.6-25d9120d83a18356f4146f2a6fe2cc8d',
'InstanceNUMATopology': '1.3-ec0030cb0402a49c96da7051c037082a',
'InstancePCIRequest': '1.3-f6d324f1c337fad4f34892ed5f484c9a',
'InstancePCIRequests': '1.1-65e38083177726d806684cb1cc0136d2',

View File

@ -932,10 +932,75 @@ class NUMATopologyTest(test.NoDBTestCase):
),
"image": {
"properties": {
"hw_cpu_policy": "shared"
}
},
"expect": fields.CPUAllocationPolicy.DEDICATED
},
{
"flavor": objects.Flavor(
extra_specs={
"hw:cpu_policy": "dedicated"
}
),
"image": {
"properties": {
}
},
"expect": fields.CPUAllocationPolicy.DEDICATED
},
{
"flavor": objects.Flavor(
extra_specs={
"hw:cpu_policy": "mixed"
}
),
"image": {
"properties": {
"hw_cpu_policy": "dedicated"
}
},
"expect": exception.ImageCPUPinningForbidden
},
{
"flavor": objects.Flavor(
extra_specs={
"hw:cpu_policy": "mixed"
}
),
"image": {
"properties": {
"hw_cpu_policy": "mixed"
}
},
"expect": fields.CPUAllocationPolicy.MIXED
},
{
"flavor": objects.Flavor(
extra_specs={
"hw:cpu_policy": "mixed"
}
),
"image": {
"properties": {
"hw_cpu_policy": "shared"
}
},
"expect": fields.CPUAllocationPolicy.MIXED
},
{
"flavor": objects.Flavor(
extra_specs={
"hw:cpu_policy": "mixed"
}
),
"image": {
"properties": {
}
},
"expect": fields.CPUAllocationPolicy.MIXED
},
{
"flavor": objects.Flavor(
extra_specs={
@ -949,6 +1014,19 @@ class NUMATopologyTest(test.NoDBTestCase):
},
"expect": exception.ImageCPUPinningForbidden
},
{
"flavor": objects.Flavor(
extra_specs={
"hw:cpu_policy": "shared"
}
),
"image": {
"properties": {
"hw_cpu_policy": "mixed"
}
},
"expect": exception.ImageCPUPinningForbidden
},
{
"flavor": objects.Flavor(
extra_specs={
@ -983,6 +1061,15 @@ class NUMATopologyTest(test.NoDBTestCase):
},
"expect": fields.CPUAllocationPolicy.DEDICATED
},
{
"flavor": objects.Flavor(),
"image": {
"properties": {
"hw_cpu_policy": "mixed"
}
},
"expect": fields.CPUAllocationPolicy.MIXED
},
{
"flavor": objects.Flavor(),
"image": {

View File

@ -1505,10 +1505,17 @@ def get_cpu_policy_constraint(
if flavor_policy == fields.CPUAllocationPolicy.DEDICATED:
cpu_policy = flavor_policy
elif flavor_policy == fields.CPUAllocationPolicy.SHARED:
elif flavor_policy == fields.CPUAllocationPolicy.MIXED:
if image_policy == fields.CPUAllocationPolicy.DEDICATED:
raise exception.ImageCPUPinningForbidden()
cpu_policy = flavor_policy
elif flavor_policy == fields.CPUAllocationPolicy.SHARED:
if image_policy in (
fields.CPUAllocationPolicy.MIXED,
fields.CPUAllocationPolicy.DEDICATED,
):
raise exception.ImageCPUPinningForbidden()
cpu_policy = flavor_policy
elif image_policy in fields.CPUAllocationPolicy.ALL:
cpu_policy = image_policy
else:
@ -1693,6 +1700,21 @@ def _get_hyperthreading_trait(
return None
# NOTE(stephenfin): This must be public as it's used elsewhere
# TODO(Huaqiang): To be filled with the logic of parsing
# 'hw:cpu_dedicated_mask' and relevant test cases in later patches once the
# code is ready to build up an instance in 'mixed' CPU allocation policy.
def get_dedicated_cpu_constraint(
flavor: 'objects.Flavor',
) -> ty.Optional[ty.Set[int]]:
"""Validate and return the requested dedicated CPU mask.
:param flavor: ``nova.objects.Flavor`` instance
:returns: The dedicated CPUs requested, else None.
"""
return None
# NOTE(stephenfin): This must be public as it's used elsewhere
def get_realtime_cpu_constraint(
flavor: 'objects.Flavor',
@ -1833,12 +1855,18 @@ def numa_get_constraints(flavor, image_meta):
with invalid value in image or flavor.
:raises: exception.InvalidRequest if there is a conflict between explicitly
and implicitly requested resources of hyperthreading traits
:raises: exception.RequiredMixedInstancePolicy if dedicated CPU mask is
provided in flavor while CPU policy is not 'mixed'.
:raises: exception.RequiredMixedOrRealtimeCPUMask the mixed policy instance
dedicated CPU mask can only be specified through either
'hw:cpu_realtime_mask' or 'hw:cpu_dedicated_mask', not both.
:returns: objects.InstanceNUMATopology, or None
"""
cpu_policy = get_cpu_policy_constraint(flavor, image_meta)
cpu_thread_policy = get_cpu_thread_policy_constraint(flavor, image_meta)
rt_mask = get_realtime_cpu_constraint(flavor, image_meta)
dedicated_cpus = get_dedicated_cpu_constraint(flavor)
emu_threads_policy = get_emulator_thread_policy_constraint(flavor)
# handle explicit VCPU/PCPU resource requests and the HW_CPU_HYPERTHREADING
@ -1904,13 +1932,36 @@ def numa_get_constraints(flavor, image_meta):
if emu_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE:
raise exception.BadRequirementEmulatorThreadsPolicy()
# 'hw:cpu_dedicated_mask' should not be defined in a flavor with
# 'shared' policy.
if dedicated_cpus:
raise exception.RequiredMixedInstancePolicy()
if rt_mask:
raise exception.RealtimeConfigurationInvalid()
elif cpu_policy == fields.CPUAllocationPolicy.DEDICATED:
# 'hw:cpu_dedicated_mask' should not be defined in a flavor with
# 'dedicated' policy.
if dedicated_cpus:
raise exception.RequiredMixedInstancePolicy()
# But for an instance with 'dedicated' CPU allocation policy, all
# CPUs are 'dedicated' CPUs, which is 1:1 pinned to a host CPU.
dedicated_cpus = set(range(flavor.vcpus))
else: # MIXED
# FIXME(huaqiang): So far, 'mixed' instance is not supported
# and the 'dedicated_cpus' variable is set to 'None' due to being not
# ready to parse 'hw:cpu_dedicated_mask'.
# The logic of parsing 'hw:cpu_dedicated_mask' should be added once
# the code is ready for setting up an 'mixed' instance.
if dedicated_cpus is None:
raise exception.RequiredMixedOrRealtimeCPUMask()
nodes = _get_numa_node_count_constraint(flavor, image_meta)
pagesize = _get_numa_pagesize_constraint(flavor, image_meta)
vpmems = get_vpmems(flavor)
dedicated_cpus = dedicated_cpus or set()
# NOTE(stephenfin): There are currently four things that will configure a
# NUMA topology for an instance:
#
@ -1920,14 +1971,13 @@ def numa_get_constraints(flavor, image_meta):
# - The use of vPMEM
if nodes or pagesize or vpmems or cpu_policy in (
fields.CPUAllocationPolicy.DEDICATED,
fields.CPUAllocationPolicy.MIXED,
):
# NOTE(huaqiang): Here we build the instance dedicated CPU set and the
# shared CPU set, through 'pcpus' and 'vcpus' respectively,
# which will be used later to calculate the per-NUMA-cell CPU set.
cpus = set(range(flavor.vcpus))
pcpus = set()
if cpu_policy == fields.CPUAllocationPolicy.DEDICATED:
pcpus = cpus
pcpus = dedicated_cpus
vcpus = cpus - pcpus
nodes = nodes or 1