hardware: Rework 'get_realtime_constraint'

Rework 'get_realtime_constraint' so that it can do the work previously
split between this and 'vcpus_realtime_topology', eliminating the need
for the latter. This provides a model for how we can parse the
'hw:cpu_dedicated_mask' extra spec in the future.

Part of blueprint use-pcpu-and-vcpu-in-one-instance

Change-Id: I3a2132db258202afe5cfa0eb95e6905030314f98
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2020-03-16 11:33:51 +00:00
parent 184a2cadf0
commit c88a35e8ea
4 changed files with 80 additions and 57 deletions

View File

@ -745,8 +745,7 @@ class API(base.Base):
# Only validate values of flavor/image so the return results of
# following 'get' functions are not used.
hardware.get_number_of_serial_ports(instance_type, image_meta)
if hardware.is_realtime_enabled(instance_type):
hardware.vcpus_realtime_topology(instance_type, image_meta)
hardware.get_realtime_cpu_constraint(instance_type, image_meta)
hardware.get_cpu_topology_constraints(instance_type, image_meta)
if validate_numa:
hardware.numa_get_constraints(instance_type, image_meta)

View File

@ -1307,6 +1307,7 @@ class NUMATopologyTest(test.NoDBTestCase):
extra_specs={
"hw:cpu_policy": fields.CPUAllocationPolicy.SHARED,
"hw:cpu_realtime": "yes",
"hw:cpu_realtime_mask": "0-3,^0",
}),
"image": {
"properties": {}
@ -3603,74 +3604,113 @@ class CPUSReservedCellTestCase(test.NoDBTestCase):
class CPURealtimeTestCase(test.NoDBTestCase):
def test_success_flavor(self):
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={"hw:cpu_realtime_mask": "^1"})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
'hw:cpu_realtime_mask': '^1',
}
)
image = objects.ImageMeta.from_dict({})
rt = hw.vcpus_realtime_topology(flavor, image)
rt = hw.get_realtime_cpu_constraint(flavor, image)
self.assertEqual(set([0, 2]), rt)
def test_success_image(self):
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={"hw:cpu_realtime_mask": "^1"})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
'hw:cpu_realtime_mask': '^1',
},
)
image = objects.ImageMeta.from_dict(
{"properties": {"hw_cpu_realtime_mask": "^0-1"}})
rt = hw.vcpus_realtime_topology(flavor, image)
rt = hw.get_realtime_cpu_constraint(flavor, image)
self.assertEqual(set([2]), rt)
def test_no_mask_configured(self):
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
},
)
image = objects.ImageMeta.from_dict({"properties": {}})
self.assertRaises(
exception.RealtimeMaskNotFoundOrInvalid,
hw.vcpus_realtime_topology, flavor, image)
hw.get_realtime_cpu_constraint, flavor, image)
def test_invalid_mask_no_rt_cpus(self):
# The mask excludes all vCPUs from being RT
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={"hw:cpu_realtime_mask": "^0-2"})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
'hw:cpu_realtime_mask': '^0-2',
},
)
image = objects.ImageMeta.from_dict({"properties": {}})
self.assertRaises(
exception.RealtimeMaskNotFoundOrInvalid,
hw.vcpus_realtime_topology, flavor, image)
hw.get_realtime_cpu_constraint, flavor, image)
def test_invalid_mask_exclude_out_of_range(self):
# The mask excludes an invalidly high vCPU number.
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={"hw:cpu_realtime_mask": "^3"})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
'hw:cpu_realtime_mask': '^3',
},
)
image = objects.ImageMeta.from_dict({"properties": {}})
self.assertRaises(
exception.RealtimeMaskNotFoundOrInvalid,
hw.vcpus_realtime_topology, flavor, image)
hw.get_realtime_cpu_constraint, flavor, image)
def test_explicit_range(self):
# The mask is not just an exclusion mask. This is unexpected but
# the code doesn't prevent it.
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={"hw:cpu_realtime_mask": "0-2,^0"})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
'hw:cpu_realtime_mask': '0-2,^0',
},
)
image = objects.ImageMeta.from_dict({"properties": {}})
rt = hw.vcpus_realtime_topology(flavor, image)
rt = hw.get_realtime_cpu_constraint(flavor, image)
self.assertEqual({1, 2}, rt)
def test_invalid_mask_no_exclusion_wo_emulator_policy(self):
# The mask has no exclusion and there's no emulator thread policy
# configured
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={"hw:cpu_realtime_mask": "0-2"})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
'hw:cpu_realtime_mask': '0-2',
},
)
image = objects.ImageMeta.from_dict({"properties": {}})
self.assertRaises(
exception.RealtimeMaskNotFoundOrInvalid,
hw.vcpus_realtime_topology, flavor, image)
hw.get_realtime_cpu_constraint, flavor, image)
def test_invalid_mask_rt_cpus_out_of_range(self):
# The mask is not just an exclusion mask, and the RT range specifies
# an invalid vCPU number.
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
extra_specs={"hw:cpu_realtime_mask": "0-3,^0"})
flavor = objects.Flavor(
vcpus=3, memory_mb=2048,
extra_specs={
'hw:cpu_realtime': 'true',
'hw:cpu_realtime_mask': '0-3,^0',
},
)
image = objects.ImageMeta.from_dict({"properties": {}})
self.assertRaises(
exception.RealtimeMaskNotFoundOrInvalid,
hw.vcpus_realtime_topology, flavor, image)
hw.get_realtime_cpu_constraint, flavor, image)
class EmulatorThreadsTestCase(test.NoDBTestCase):

View File

@ -1674,37 +1674,26 @@ def _get_hyperthreading_trait(
return None
def _get_realtime_constraint(
# NOTE(stephenfin): This must be public as it's used elsewhere
def get_realtime_cpu_constraint(
flavor: 'objects.Flavor',
image_meta: 'objects.ImageMeta',
) -> ty.Optional[str]:
) -> ty.Optional[ty.Set[int]]:
"""Validate and return the requested realtime CPU mask.
:param flavor: ``nova.objects.Flavor`` instance
:param image_meta: ``nova.objects.ImageMeta`` instance
:returns: The realtime CPU mask requested, else None.
:returns: The realtime CPU set requested, else None.
"""
if not is_realtime_enabled(flavor):
return None
flavor_mask, image_mask = _get_flavor_image_meta(
'cpu_realtime_mask', flavor, image_meta)
# Image masks are used ahead of flavor masks as they will have more
# specific requirements
return image_mask or flavor_mask
def vcpus_realtime_topology(
flavor: 'objects.Flavor',
image_meta: 'objects.ImageMeta',
) -> ty.Set[int]:
"""Determines instance vCPUs used as RT for a given spec.
:param flavor: ``nova.objects.Flavor`` instance
:param image_meta: ``nova.objects.ImageMeta`` instance
:raises: exception.RealtimeMaskNotFoundOrInvalid if mask was not found or
is invalid.
:returns: The realtime CPU mask requested.
"""
mask = _get_realtime_constraint(flavor, image_meta)
mask = image_mask or flavor_mask
if not mask:
raise exception.RealtimeMaskNotFoundOrInvalid()
@ -1854,7 +1843,7 @@ def numa_get_constraints(flavor, image_meta):
cpu_policy = get_cpu_policy_constraint(flavor, image_meta)
cpu_thread_policy = get_cpu_thread_policy_constraint(flavor, image_meta)
rt_mask = _get_realtime_constraint(flavor, image_meta)
rt_mask = get_realtime_cpu_constraint(flavor, image_meta)
emu_threads_policy = get_emulator_thread_policy_constraint(flavor)
# handle explicit VCPU/PCPU resource requests and the HW_CPU_HYPERTHREADING
@ -1920,14 +1909,11 @@ def numa_get_constraints(flavor, image_meta):
if emu_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE:
raise exception.BadRequirementEmulatorThreadsPolicy()
if is_realtime_enabled(flavor):
if rt_mask:
raise exception.RealtimeConfigurationInvalid()
return numa_topology
if is_realtime_enabled(flavor) and not rt_mask:
raise exception.RealtimeMaskNotFoundOrInvalid()
if numa_topology:
for cell in numa_topology.cells:
cell.cpu_policy = cpu_policy

View File

@ -4990,7 +4990,7 @@ class LibvirtDriver(driver.ComputeDriver):
return pin_cpuset
def _get_emulatorpin_cpuset(self, vcpu, object_numa_cell, vcpus_rt,
emulator_threads_policy, wants_realtime,
emulator_threads_policy,
pin_cpuset):
"""Returns a set of cpu_ids to add to the cpuset for emulator threads
with the following caveats:
@ -5028,7 +5028,7 @@ class LibvirtDriver(driver.ComputeDriver):
'req': sorted(shared_ids)})
raise exception.Invalid(msg)
emulatorpin_cpuset = cpuset
elif not wants_realtime or vcpu not in vcpus_rt:
elif not vcpus_rt or vcpu not in vcpus_rt:
emulatorpin_cpuset = pin_cpuset.cpuset
return emulatorpin_cpuset
@ -5115,10 +5115,8 @@ class LibvirtDriver(driver.ComputeDriver):
instance_numa_topology.emulator_threads_policy)
# Set realtime scheduler for CPUTune
vcpus_rt = set([])
wants_realtime = hardware.is_realtime_enabled(flavor)
if wants_realtime:
vcpus_rt = hardware.vcpus_realtime_topology(flavor, image_meta)
vcpus_rt = hardware.get_realtime_cpu_constraint(flavor, image_meta)
if vcpus_rt:
vcpusched = vconfig.LibvirtConfigGuestCPUTuneVCPUSched()
designer.set_vcpu_realtime_scheduler(
vcpusched, vcpus_rt, CONF.libvirt.realtime_scheduler_priority)
@ -5142,7 +5140,7 @@ class LibvirtDriver(driver.ComputeDriver):
emu_pin_cpuset = self._get_emulatorpin_cpuset(
cpu, object_numa_cell, vcpus_rt,
emulator_threads_policy, wants_realtime, pin_cpuset)
emulator_threads_policy, pin_cpuset)
guest_cpu_tune.emulatorpin.cpuset.update(emu_pin_cpuset)
# TODO(berrange) When the guest has >1 NUMA node, it will