diff --git a/nova/exception.py b/nova/exception.py index b9a08df765f2..3b2cc0b835d2 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2123,3 +2123,13 @@ class PlacementNotConfigured(NovaException): msg_fmt = _("This compute is not configured to talk to the placement " "service. Configure the [placement] section of nova.conf " "and restart the service.") + + +class InvalidEmulatorThreadsPolicy(Invalid): + msg_fmt = _("CPU emulator threads option requested is invalid, " + "given: '%(requested)s', available: '%(available)s'.") + + +class BadRequirementEmulatorThreadsPolicy(Invalid): + msg_fmt = _("An isolated CPU emulator threads option requires a dedicated " + "CPU policy option.") diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py index 003859319367..d49699f7011a 100644 --- a/nova/tests/unit/virt/test_hardware.py +++ b/nova/tests/unit/virt/test_hardware.py @@ -1197,6 +1197,66 @@ class NUMATopologyTest(test.NoDBTestCase): }, "expect": exception.RealtimeMaskNotFoundOrInvalid, }, + { # We pass an invalid option + "flavor": objects.Flavor(vcpus=16, memory_mb=2048, + extra_specs={ + "hw:emulator_threads_policy": "foo", + }), + "image": { + "properties": {} + }, + "expect": exception.InvalidEmulatorThreadsPolicy, + }, + { # We request emulator threads option without numa topology + "flavor": objects.Flavor(vcpus=16, memory_mb=2048, + extra_specs={ + "hw:emulator_threads_policy": "isolate", + }), + "image": { + "properties": {} + }, + "expect": exception.BadRequirementEmulatorThreadsPolicy, + }, + { # We request a valid emulator threads options with + # cpu_policy based from flavor + "flavor": objects.Flavor(vcpus=4, memory_mb=2048, + extra_specs={ + "hw:emulator_threads_policy": "isolate", + "hw:cpu_policy": "dedicated", + }), + "image": { + "properties": {} + }, + "expect": objects.InstanceNUMATopology( + emulator_threads_policy= + fields.CPUEmulatorThreadsPolicy.ISOLATE, + cells=[ + objects.InstanceNUMACell( + id=0, cpuset=set([0, 1, 2, 3]), memory=2048, + cpu_policy=fields.CPUAllocationPolicy.DEDICATED, + )]), + }, + { # We request a valid emulator threads options with cpu + # policy based from image + "flavor": objects.Flavor(vcpus=4, memory_mb=2048, + extra_specs={ + "hw:emulator_threads_policy": "isolate", + }), + "image": { + "properties": { + "hw_cpu_policy": "dedicated", + } + }, + "expect": objects.InstanceNUMATopology( + emulator_threads_policy= + fields.CPUEmulatorThreadsPolicy.ISOLATE, + cells=[ + objects.InstanceNUMACell( + id=0, cpuset=set([0, 1, 2, 3]), memory=2048, + cpu_policy=fields.CPUAllocationPolicy.DEDICATED, + )]), + }, + ] for testitem in testdata: @@ -1216,6 +1276,10 @@ class NUMATopologyTest(test.NoDBTestCase): self.assertIsNotNone(topology) self.assertEqual(len(testitem["expect"].cells), len(topology.cells)) + self.assertEqual( + testitem["expect"].emulator_threads_isolated, + topology.emulator_threads_isolated) + for i in range(len(topology.cells)): self.assertEqual(testitem["expect"].cells[i].id, topology.cells[i].id) diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index ee5d21f9043c..da5ac036e782 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -1198,6 +1198,30 @@ def _numa_get_constraints_auto(nodes, flavor): return objects.InstanceNUMATopology(cells=cells) +def get_emulator_threads_constraint(flavor, image_meta): + """Determines the emulator threads policy""" + emu_threads_policy = flavor.get('extra_specs', {}).get( + 'hw:emulator_threads_policy') + LOG.debug("emulator threads policy constraint: %s", emu_threads_policy) + + if not emu_threads_policy: + return + + if emu_threads_policy not in fields.CPUEmulatorThreadsPolicy.ALL: + raise exception.InvalidEmulatorThreadsPolicy( + requested=emu_threads_policy, + available=str(fields.CPUEmulatorThreadsPolicy.ALL)) + + if emu_threads_policy == fields.CPUEmulatorThreadsPolicy.ISOLATE: + # In order to make available emulator threads policy, a dedicated + # CPU policy is necessary. + cpu_policy = _get_cpu_policy_constraints(flavor, image_meta) + if cpu_policy != fields.CPUAllocationPolicy.DEDICATED: + raise exception.BadRequirementEmulatorThreadsPolicy() + + return emu_threads_policy + + def _validate_numa_nodes(nodes): """Validate NUMA nodes number @@ -1300,6 +1324,7 @@ def numa_get_constraints(flavor, image_meta): cpu_policy = _get_cpu_policy_constraints(flavor, image_meta) cpu_thread_policy = _get_cpu_thread_policy_constraints(flavor, image_meta) rt_mask = _get_realtime_mask(flavor, image_meta) + emu_thread_policy = get_emulator_threads_constraint(flavor, image_meta) # sanity checks @@ -1322,13 +1347,16 @@ def numa_get_constraints(flavor, image_meta): cell.cpu_thread_policy = cpu_thread_policy else: single_cell = objects.InstanceNUMACell( - id=0, - cpuset=set(range(flavor.vcpus)), - memory=flavor.memory_mb, - cpu_policy=cpu_policy, - cpu_thread_policy=cpu_thread_policy) + id=0, + cpuset=set(range(flavor.vcpus)), + memory=flavor.memory_mb, + cpu_policy=cpu_policy, + cpu_thread_policy=cpu_thread_policy) numa_topology = objects.InstanceNUMATopology(cells=[single_cell]) + if emu_thread_policy: + numa_topology.emulator_threads_policy = emu_thread_policy + return numa_topology @@ -1365,6 +1393,10 @@ def numa_fit_instance_to_host( 'actual': len(host_topology)}) return + emulator_threads_policy = None + if 'emulator_threads_policy' in instance_topology: + emulator_threads_policy = instance_topology.emulator_threads_policy + # TODO(ndipanov): We may want to sort permutations differently # depending on whether we want packing/spreading over NUMA nodes for host_cell_perm in itertools.permutations( @@ -1389,7 +1421,9 @@ def numa_fit_instance_to_host( if not pci_requests or ((pci_stats is not None) and pci_stats.support_requests(pci_requests, cells)): - return objects.InstanceNUMATopology(cells=cells) + return objects.InstanceNUMATopology( + cells=cells, + emulator_threads_policy=emulator_threads_policy) def numa_get_reserved_huge_pages(): @@ -1563,8 +1597,11 @@ def instance_topology_from_instance(instance): cpu_thread_policy=cell.get('cpu_thread_policy'), cpuset_reserved=cell.get('cpuset_reserved')) for cell in dict_cells] + emulator_threads_policy = instance_numa_topology.get( + 'emulator_threads_policy') instance_numa_topology = objects.InstanceNUMATopology( - cells=cells) + cells=cells, + emulator_threads_policy=emulator_threads_policy) return instance_numa_topology