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:
parent
184a2cadf0
commit
c88a35e8ea
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue