diff --git a/nova/compute/api.py b/nova/compute/api.py index c7b4e49c894d..99d33e87f6df 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -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) diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py index 36aa7c48e441..a10c4ec4de9a 100644 --- a/nova/tests/unit/virt/test_hardware.py +++ b/nova/tests/unit/virt/test_hardware.py @@ -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): diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index f421f189caee..d5a98bd5b4a5 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -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 diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 08dd3446ad12..852ec9ca6551 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -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