diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py index cffb8fc55870..a7ec674d1ad8 100644 --- a/nova/tests/unit/virt/test_hardware.py +++ b/nova/tests/unit/virt/test_hardware.py @@ -3658,7 +3658,8 @@ class CPURealtimeTestCase(test.NoDBTestCase): exception.RealtimeMaskNotFoundOrInvalid, hw.vcpus_realtime_topology, flavor, image) - def test_mask_badly_configured(self): + 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"}) image = objects.ImageMeta.from_dict({"properties": {}}) @@ -3666,6 +3667,44 @@ class CPURealtimeTestCase(test.NoDBTestCase): exception.RealtimeMaskNotFoundOrInvalid, hw.vcpus_realtime_topology, 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"}) + image = objects.ImageMeta.from_dict({"properties": {}}) + self.assertRaises( + exception.RealtimeMaskNotFoundOrInvalid, + hw.vcpus_realtime_topology, 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"}) + image = objects.ImageMeta.from_dict({"properties": {}}) + rt = hw.vcpus_realtime_topology(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"}) + image = objects.ImageMeta.from_dict({"properties": {}}) + self.assertRaises( + exception.RealtimeMaskNotFoundOrInvalid, + hw.vcpus_realtime_topology, 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"}) + image = objects.ImageMeta.from_dict({"properties": {}}) + self.assertRaises( + exception.RealtimeMaskNotFoundOrInvalid, + hw.vcpus_realtime_topology, flavor, image) + class EmulatorThreadsTestCase(test.NoDBTestCase): diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index b32e38068f5e..37dd6d1618eb 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -1720,10 +1720,21 @@ def vcpus_realtime_topology( if not mask: raise exception.RealtimeMaskNotFoundOrInvalid() + vcpus_set = set(range(flavor.vcpus)) vcpus_rt = parse_cpu_spec("0-%d,%s" % (flavor.vcpus - 1, mask)) - if len(vcpus_rt) < 1: + + if not vcpus_rt: raise exception.RealtimeMaskNotFoundOrInvalid() + if vcpus_set == vcpus_rt: + raise exception.RealtimeMaskNotFoundOrInvalid() + + if not vcpus_rt.issubset(vcpus_set): + msg = _("Realtime policy vCPU(s) mask is configured with RT vCPUs " + "that are not a subset of the vCPUs in the flavor. See " + "hw:cpu_realtime_mask or hw_cpu_realtime_mask") + raise exception.RealtimeMaskNotFoundOrInvalid(msg) + return vcpus_rt diff --git a/releasenotes/notes/bug-1884231-16acf297d88b122e.yaml b/releasenotes/notes/bug-1884231-16acf297d88b122e.yaml new file mode 100644 index 000000000000..a9096b9332f2 --- /dev/null +++ b/releasenotes/notes/bug-1884231-16acf297d88b122e.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Previously, it was possible to specify values for the + ``hw:cpu_realtime_mask`` extra spec that were not within the range of valid + instances cores. This value is now correctly validated.