Merge "hardware: Rework 'get_realtime_constraint'"
This commit is contained in:
commit
d4d8ea1da3
|
@ -744,8 +744,7 @@ class API(base.Base):
|
||||||
# Only validate values of flavor/image so the return results of
|
# Only validate values of flavor/image so the return results of
|
||||||
# following 'get' functions are not used.
|
# following 'get' functions are not used.
|
||||||
hardware.get_number_of_serial_ports(instance_type, image_meta)
|
hardware.get_number_of_serial_ports(instance_type, image_meta)
|
||||||
if hardware.is_realtime_enabled(instance_type):
|
hardware.get_realtime_cpu_constraint(instance_type, image_meta)
|
||||||
hardware.vcpus_realtime_topology(instance_type, image_meta)
|
|
||||||
hardware.get_cpu_topology_constraints(instance_type, image_meta)
|
hardware.get_cpu_topology_constraints(instance_type, image_meta)
|
||||||
if validate_numa:
|
if validate_numa:
|
||||||
hardware.numa_get_constraints(instance_type, image_meta)
|
hardware.numa_get_constraints(instance_type, image_meta)
|
||||||
|
|
|
@ -1307,6 +1307,7 @@ class NUMATopologyTest(test.NoDBTestCase):
|
||||||
extra_specs={
|
extra_specs={
|
||||||
"hw:cpu_policy": fields.CPUAllocationPolicy.SHARED,
|
"hw:cpu_policy": fields.CPUAllocationPolicy.SHARED,
|
||||||
"hw:cpu_realtime": "yes",
|
"hw:cpu_realtime": "yes",
|
||||||
|
"hw:cpu_realtime_mask": "0-3,^0",
|
||||||
}),
|
}),
|
||||||
"image": {
|
"image": {
|
||||||
"properties": {}
|
"properties": {}
|
||||||
|
@ -3603,74 +3604,113 @@ class CPUSReservedCellTestCase(test.NoDBTestCase):
|
||||||
|
|
||||||
class CPURealtimeTestCase(test.NoDBTestCase):
|
class CPURealtimeTestCase(test.NoDBTestCase):
|
||||||
def test_success_flavor(self):
|
def test_success_flavor(self):
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={"hw:cpu_realtime_mask": "^1"})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
'hw:cpu_realtime_mask': '^1',
|
||||||
|
}
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict({})
|
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)
|
self.assertEqual(set([0, 2]), rt)
|
||||||
|
|
||||||
def test_success_image(self):
|
def test_success_image(self):
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={"hw:cpu_realtime_mask": "^1"})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
'hw:cpu_realtime_mask': '^1',
|
||||||
|
},
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict(
|
image = objects.ImageMeta.from_dict(
|
||||||
{"properties": {"hw_cpu_realtime_mask": "^0-1"}})
|
{"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)
|
self.assertEqual(set([2]), rt)
|
||||||
|
|
||||||
def test_no_mask_configured(self):
|
def test_no_mask_configured(self):
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
},
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict({"properties": {}})
|
image = objects.ImageMeta.from_dict({"properties": {}})
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.RealtimeMaskNotFoundOrInvalid,
|
exception.RealtimeMaskNotFoundOrInvalid,
|
||||||
hw.vcpus_realtime_topology, flavor, image)
|
hw.get_realtime_cpu_constraint, flavor, image)
|
||||||
|
|
||||||
def test_invalid_mask_no_rt_cpus(self):
|
def test_invalid_mask_no_rt_cpus(self):
|
||||||
# The mask excludes all vCPUs from being RT
|
# The mask excludes all vCPUs from being RT
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={"hw:cpu_realtime_mask": "^0-2"})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
'hw:cpu_realtime_mask': '^0-2',
|
||||||
|
},
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict({"properties": {}})
|
image = objects.ImageMeta.from_dict({"properties": {}})
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.RealtimeMaskNotFoundOrInvalid,
|
exception.RealtimeMaskNotFoundOrInvalid,
|
||||||
hw.vcpus_realtime_topology, flavor, image)
|
hw.get_realtime_cpu_constraint, flavor, image)
|
||||||
|
|
||||||
def test_invalid_mask_exclude_out_of_range(self):
|
def test_invalid_mask_exclude_out_of_range(self):
|
||||||
# The mask excludes an invalidly high vCPU number.
|
# The mask excludes an invalidly high vCPU number.
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={"hw:cpu_realtime_mask": "^3"})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
'hw:cpu_realtime_mask': '^3',
|
||||||
|
},
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict({"properties": {}})
|
image = objects.ImageMeta.from_dict({"properties": {}})
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.RealtimeMaskNotFoundOrInvalid,
|
exception.RealtimeMaskNotFoundOrInvalid,
|
||||||
hw.vcpus_realtime_topology, flavor, image)
|
hw.get_realtime_cpu_constraint, flavor, image)
|
||||||
|
|
||||||
def test_explicit_range(self):
|
def test_explicit_range(self):
|
||||||
# The mask is not just an exclusion mask. This is unexpected but
|
# The mask is not just an exclusion mask. This is unexpected but
|
||||||
# the code doesn't prevent it.
|
# the code doesn't prevent it.
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={"hw:cpu_realtime_mask": "0-2,^0"})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
'hw:cpu_realtime_mask': '0-2,^0',
|
||||||
|
},
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict({"properties": {}})
|
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)
|
self.assertEqual({1, 2}, rt)
|
||||||
|
|
||||||
def test_invalid_mask_no_exclusion_wo_emulator_policy(self):
|
def test_invalid_mask_no_exclusion_wo_emulator_policy(self):
|
||||||
# The mask has no exclusion and there's no emulator thread policy
|
# The mask has no exclusion and there's no emulator thread policy
|
||||||
# configured
|
# configured
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={"hw:cpu_realtime_mask": "0-2"})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
'hw:cpu_realtime_mask': '0-2',
|
||||||
|
},
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict({"properties": {}})
|
image = objects.ImageMeta.from_dict({"properties": {}})
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.RealtimeMaskNotFoundOrInvalid,
|
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):
|
def test_invalid_mask_rt_cpus_out_of_range(self):
|
||||||
# The mask is not just an exclusion mask, and the RT range specifies
|
# The mask is not just an exclusion mask, and the RT range specifies
|
||||||
# an invalid vCPU number.
|
# an invalid vCPU number.
|
||||||
flavor = objects.Flavor(vcpus=3, memory_mb=2048,
|
flavor = objects.Flavor(
|
||||||
extra_specs={"hw:cpu_realtime_mask": "0-3,^0"})
|
vcpus=3, memory_mb=2048,
|
||||||
|
extra_specs={
|
||||||
|
'hw:cpu_realtime': 'true',
|
||||||
|
'hw:cpu_realtime_mask': '0-3,^0',
|
||||||
|
},
|
||||||
|
)
|
||||||
image = objects.ImageMeta.from_dict({"properties": {}})
|
image = objects.ImageMeta.from_dict({"properties": {}})
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.RealtimeMaskNotFoundOrInvalid,
|
exception.RealtimeMaskNotFoundOrInvalid,
|
||||||
hw.vcpus_realtime_topology, flavor, image)
|
hw.get_realtime_cpu_constraint, flavor, image)
|
||||||
|
|
||||||
|
|
||||||
class EmulatorThreadsTestCase(test.NoDBTestCase):
|
class EmulatorThreadsTestCase(test.NoDBTestCase):
|
||||||
|
|
|
@ -1674,37 +1674,26 @@ def _get_hyperthreading_trait(
|
||||||
return None
|
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',
|
flavor: 'objects.Flavor',
|
||||||
image_meta: 'objects.ImageMeta',
|
image_meta: 'objects.ImageMeta',
|
||||||
) -> ty.Optional[str]:
|
) -> ty.Optional[ty.Set[int]]:
|
||||||
"""Validate and return the requested realtime CPU mask.
|
"""Validate and return the requested realtime CPU mask.
|
||||||
|
|
||||||
:param flavor: ``nova.objects.Flavor`` instance
|
:param flavor: ``nova.objects.Flavor`` instance
|
||||||
:param image_meta: ``nova.objects.ImageMeta`` 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(
|
flavor_mask, image_mask = _get_flavor_image_meta(
|
||||||
'cpu_realtime_mask', flavor, image_meta)
|
'cpu_realtime_mask', flavor, image_meta)
|
||||||
|
|
||||||
# Image masks are used ahead of flavor masks as they will have more
|
# Image masks are used ahead of flavor masks as they will have more
|
||||||
# specific requirements
|
# specific requirements
|
||||||
return image_mask or flavor_mask
|
mask = 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)
|
|
||||||
if not mask:
|
if not mask:
|
||||||
raise exception.RealtimeMaskNotFoundOrInvalid()
|
raise exception.RealtimeMaskNotFoundOrInvalid()
|
||||||
|
|
||||||
|
@ -1854,7 +1843,7 @@ def numa_get_constraints(flavor, image_meta):
|
||||||
|
|
||||||
cpu_policy = get_cpu_policy_constraint(flavor, image_meta)
|
cpu_policy = get_cpu_policy_constraint(flavor, image_meta)
|
||||||
cpu_thread_policy = get_cpu_thread_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)
|
emu_threads_policy = get_emulator_thread_policy_constraint(flavor)
|
||||||
|
|
||||||
# handle explicit VCPU/PCPU resource requests and the HW_CPU_HYPERTHREADING
|
# 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:
|
if emu_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE:
|
||||||
raise exception.BadRequirementEmulatorThreadsPolicy()
|
raise exception.BadRequirementEmulatorThreadsPolicy()
|
||||||
|
|
||||||
if is_realtime_enabled(flavor):
|
if rt_mask:
|
||||||
raise exception.RealtimeConfigurationInvalid()
|
raise exception.RealtimeConfigurationInvalid()
|
||||||
|
|
||||||
return numa_topology
|
return numa_topology
|
||||||
|
|
||||||
if is_realtime_enabled(flavor) and not rt_mask:
|
|
||||||
raise exception.RealtimeMaskNotFoundOrInvalid()
|
|
||||||
|
|
||||||
if numa_topology:
|
if numa_topology:
|
||||||
for cell in numa_topology.cells:
|
for cell in numa_topology.cells:
|
||||||
cell.cpu_policy = cpu_policy
|
cell.cpu_policy = cpu_policy
|
||||||
|
|
|
@ -4973,7 +4973,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
return pin_cpuset
|
return pin_cpuset
|
||||||
|
|
||||||
def _get_emulatorpin_cpuset(self, vcpu, object_numa_cell, vcpus_rt,
|
def _get_emulatorpin_cpuset(self, vcpu, object_numa_cell, vcpus_rt,
|
||||||
emulator_threads_policy, wants_realtime,
|
emulator_threads_policy,
|
||||||
pin_cpuset):
|
pin_cpuset):
|
||||||
"""Returns a set of cpu_ids to add to the cpuset for emulator threads
|
"""Returns a set of cpu_ids to add to the cpuset for emulator threads
|
||||||
with the following caveats:
|
with the following caveats:
|
||||||
|
@ -5011,7 +5011,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
'req': sorted(shared_ids)})
|
'req': sorted(shared_ids)})
|
||||||
raise exception.Invalid(msg)
|
raise exception.Invalid(msg)
|
||||||
emulatorpin_cpuset = cpuset
|
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
|
emulatorpin_cpuset = pin_cpuset.cpuset
|
||||||
|
|
||||||
return emulatorpin_cpuset
|
return emulatorpin_cpuset
|
||||||
|
@ -5098,10 +5098,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
instance_numa_topology.emulator_threads_policy)
|
instance_numa_topology.emulator_threads_policy)
|
||||||
|
|
||||||
# Set realtime scheduler for CPUTune
|
# Set realtime scheduler for CPUTune
|
||||||
vcpus_rt = set([])
|
vcpus_rt = hardware.get_realtime_cpu_constraint(flavor, image_meta)
|
||||||
wants_realtime = hardware.is_realtime_enabled(flavor)
|
if vcpus_rt:
|
||||||
if wants_realtime:
|
|
||||||
vcpus_rt = hardware.vcpus_realtime_topology(flavor, image_meta)
|
|
||||||
vcpusched = vconfig.LibvirtConfigGuestCPUTuneVCPUSched()
|
vcpusched = vconfig.LibvirtConfigGuestCPUTuneVCPUSched()
|
||||||
designer.set_vcpu_realtime_scheduler(
|
designer.set_vcpu_realtime_scheduler(
|
||||||
vcpusched, vcpus_rt, CONF.libvirt.realtime_scheduler_priority)
|
vcpusched, vcpus_rt, CONF.libvirt.realtime_scheduler_priority)
|
||||||
|
@ -5125,7 +5123,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||||
|
|
||||||
emu_pin_cpuset = self._get_emulatorpin_cpuset(
|
emu_pin_cpuset = self._get_emulatorpin_cpuset(
|
||||||
cpu, object_numa_cell, vcpus_rt,
|
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)
|
guest_cpu_tune.emulatorpin.cpuset.update(emu_pin_cpuset)
|
||||||
|
|
||||||
# TODO(berrange) When the guest has >1 NUMA node, it will
|
# TODO(berrange) When the guest has >1 NUMA node, it will
|
||||||
|
|
Loading…
Reference in New Issue