Browse Source

Merge "hardware: Rework 'get_realtime_constraint'"

changes/32/738432/1
Zuul 1 week ago
committed by Gerrit Code Review
parent
commit
d4d8ea1da3
4 changed files with 80 additions and 57 deletions
  1. +1
    -2
      nova/compute/api.py
  2. +64
    -24
      nova/tests/unit/virt/test_hardware.py
  3. +10
    -24
      nova/virt/hardware.py
  4. +5
    -7
      nova/virt/libvirt/driver.py

+ 1
- 2
nova/compute/api.py View File

@@ -744,8 +744,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)


+ 64
- 24
nova/tests/unit/virt/test_hardware.py 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):


+ 10
- 24
nova/virt/hardware.py 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


+ 5
- 7
nova/virt/libvirt/driver.py View File

@@ -4973,7 +4973,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:
@@ -5011,7 +5011,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
@@ -5098,10 +5098,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)
@@ -5125,7 +5123,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…
Cancel
Save