Merge "hardware: Start accounting for networks in NUMA fitting"
This commit is contained in:
commit
98d09d519f
|
@ -1801,6 +1801,42 @@ class VirtNUMAHostTopologyTestCase(test.NoDBTestCase):
|
|||
self.assertIsInstance(fitted_instance2, objects.InstanceNUMATopology)
|
||||
self.assertEqual(2, fitted_instance2.cells[0].id)
|
||||
|
||||
@mock.patch.object(hw, '_numa_cells_support_network_metadata',
|
||||
return_value=True)
|
||||
def test_get_fitting_success_limits_with_networks(self, mock_supports):
|
||||
network_metadata = objects.NetworkMetadata(
|
||||
physnets=set(), tunneled=False)
|
||||
limits = objects.NUMATopologyLimits(
|
||||
cpu_allocation_ratio=2.0,
|
||||
ram_allocation_ratio=2.0,
|
||||
network_metadata=network_metadata)
|
||||
|
||||
fitted_instance = hw.numa_fit_instance_to_host(
|
||||
self.host, self.instance1, limits=limits)
|
||||
|
||||
self.assertIsInstance(fitted_instance, objects.InstanceNUMATopology)
|
||||
mock_supports.assert_called_once_with(
|
||||
self.host, [self.host.cells[0]], network_metadata)
|
||||
|
||||
@mock.patch.object(hw, '_numa_cells_support_network_metadata',
|
||||
return_value=False)
|
||||
def test_get_fitting_fails_limits_with_networks(self, mock_supports):
|
||||
network_metadata = objects.NetworkMetadata(
|
||||
physnets=set(), tunneled=False)
|
||||
limits = objects.NUMATopologyLimits(
|
||||
cpu_allocation_ratio=2.0,
|
||||
ram_allocation_ratio=2.0,
|
||||
network_metadata=network_metadata)
|
||||
|
||||
fitted_instance = hw.numa_fit_instance_to_host(
|
||||
self.host, self.instance1, limits=limits)
|
||||
|
||||
self.assertIsNone(fitted_instance)
|
||||
mock_supports.assert_has_calls([
|
||||
mock.call(self.host, [self.host.cells[0]], network_metadata),
|
||||
mock.call(self.host, [self.host.cells[1]], network_metadata),
|
||||
])
|
||||
|
||||
def test_get_fitting_pci_success(self):
|
||||
pci_request = objects.InstancePCIRequest(count=1,
|
||||
spec=[{'vendor_id': '8086'}])
|
||||
|
@ -3295,3 +3331,118 @@ class EmulatorThreadsTestCase(test.NoDBTestCase):
|
|||
|
||||
self.assertEqual({0: 2, 1: 4}, inst_topo.cells[0].cpu_pinning)
|
||||
self.assertEqual(set([1]), inst_topo.cells[0].cpuset_reserved)
|
||||
|
||||
|
||||
class NetworkRequestSupportTestCase(test.NoDBTestCase):
|
||||
"""Validate behavior of '_numa_cells_support_network_metadata'."""
|
||||
|
||||
def setUp(self):
|
||||
super(NetworkRequestSupportTestCase, self).setUp()
|
||||
|
||||
self.network_a = objects.NetworkMetadata(
|
||||
physnets=set(['foo', 'bar']), tunneled=False)
|
||||
self.network_b = objects.NetworkMetadata(
|
||||
physnets=set(), tunneled=True)
|
||||
|
||||
self.host = objects.NUMATopology(cells=[
|
||||
objects.NUMACell(id=1, cpuset=set([1, 2]), memory=4096,
|
||||
cpu_usage=2, memory_usage=0, mempages=[],
|
||||
siblings=[set([1]), set([2])],
|
||||
pinned_cpus=set([]),
|
||||
network_metadata=self.network_a),
|
||||
objects.NUMACell(id=2, cpuset=set([3, 4]), memory=4096,
|
||||
cpu_usage=2, memory_usage=0, mempages=[],
|
||||
siblings=[set([3]), set([4])],
|
||||
pinned_cpus=set([]),
|
||||
network_metadata=self.network_b)])
|
||||
|
||||
self.instance = objects.InstanceNUMATopology(cells=[
|
||||
objects.InstanceNUMACell(id=0, cpuset=set([1, 2]),
|
||||
memory=2048)])
|
||||
|
||||
def test_no_required_networks(self):
|
||||
"""Validate behavior if the user doesn't request networks.
|
||||
|
||||
No networks == no affinity to worry about.
|
||||
"""
|
||||
network_metadata = objects.NetworkMetadata(
|
||||
physnets=set(), tunneled=False)
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[0]], network_metadata)
|
||||
self.assertTrue(supports)
|
||||
|
||||
def test_missing_networks(self):
|
||||
"""Validate behavior with a physical network without affinity.
|
||||
|
||||
If we haven't recorded NUMA affinity for a given physical network, we
|
||||
clearly shouldn't fail to build.
|
||||
"""
|
||||
network_metadata = objects.NetworkMetadata(
|
||||
physnets=set(['baz']), tunneled=False)
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[0]], network_metadata)
|
||||
self.assertTrue(supports)
|
||||
|
||||
def test_physnet_networks(self):
|
||||
"""Validate behavior with a single physical network."""
|
||||
network_metadata = objects.NetworkMetadata(
|
||||
physnets=set(['foo']), tunneled=False)
|
||||
|
||||
# The required network is affined to host NUMA node 0 so this should
|
||||
# pass
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[0]], network_metadata)
|
||||
self.assertTrue(supports)
|
||||
|
||||
# ...while it should fail for the other host NUMA node
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[1]], network_metadata)
|
||||
self.assertFalse(supports)
|
||||
|
||||
# ...but it will pass if we chose both host NUMA nodes
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, self.host.cells, network_metadata)
|
||||
self.assertTrue(supports)
|
||||
|
||||
def test_tunnel_network(self):
|
||||
"""Validate behavior with a single tunneled network.
|
||||
|
||||
Neutron currently only allows a single tunnel provider network so this
|
||||
is realistic anyway.
|
||||
"""
|
||||
network_metadata = objects.NetworkMetadata(
|
||||
physnets=set(), tunneled=True)
|
||||
|
||||
# The required network is affined to host NUMA node 1 so this should
|
||||
# fail
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[0]], network_metadata)
|
||||
self.assertFalse(supports)
|
||||
|
||||
# ...but it will pass for the other host NUMA node
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[1]], network_metadata)
|
||||
self.assertTrue(supports)
|
||||
|
||||
def test_multiple_networks(self):
|
||||
"""Validate behavior with multiple networks.
|
||||
|
||||
If a we request multiple networks that are spread across host NUMA
|
||||
nodes, we're going to need to use multiple host instances.
|
||||
"""
|
||||
network_metadata = objects.NetworkMetadata(
|
||||
physnets=set(['foo', 'bar']), tunneled=True)
|
||||
|
||||
# Because the requested networks are spread across multiple host nodes,
|
||||
# this should fail because no single node can satisfy the request
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[0]], network_metadata)
|
||||
self.assertFalse(supports)
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, [self.host.cells[1]], network_metadata)
|
||||
self.assertFalse(supports)
|
||||
|
||||
# ...but it will pass if we provide all necessary nodes
|
||||
supports = hw._numa_cells_support_network_metadata(
|
||||
self.host, self.host.cells, network_metadata)
|
||||
self.assertTrue(supports)
|
||||
|
|
|
@ -1536,6 +1536,86 @@ def numa_get_constraints(flavor, image_meta):
|
|||
return numa_topology
|
||||
|
||||
|
||||
def _numa_cells_support_network_metadata(
|
||||
host_topology, # type: objects.NUMATopology
|
||||
chosen_host_cells, # type: List[objects.NUMACell]
|
||||
network_metadata # type: objects.NetworkMetadata
|
||||
):
|
||||
# type: (...) -> bool
|
||||
"""Determine whether the cells can accept the network requests.
|
||||
|
||||
:param host_topology: The entire host topology, used to find non-chosen
|
||||
host cells.
|
||||
:param chosen_host_cells: List of NUMACells to extract possible network
|
||||
NUMA affinity from.
|
||||
:param network_metadata: The combined summary of physnets and tunneled
|
||||
networks required by this topology or None.
|
||||
|
||||
:return: True if any NUMA affinity constraints for requested networks can
|
||||
be satisfied, else False
|
||||
"""
|
||||
if not network_metadata:
|
||||
return True
|
||||
|
||||
required_physnets = None # type: Set[str]
|
||||
if 'physnets' in network_metadata:
|
||||
# use set() to avoid modifying the original data structure
|
||||
required_physnets = set(network_metadata.physnets)
|
||||
|
||||
required_tunnel = False # type: bool
|
||||
if 'tunneled' in network_metadata:
|
||||
required_tunnel = network_metadata.tunneled
|
||||
|
||||
if required_physnets:
|
||||
# identify requested physnets that have an affinity to any of our
|
||||
# chosen host NUMA cells
|
||||
for host_cell in chosen_host_cells:
|
||||
if 'network_metadata' not in host_cell:
|
||||
continue
|
||||
|
||||
# if one of these cells provides affinity for one or more physnets,
|
||||
# drop said physnet(s) from the list we're searching for
|
||||
required_physnets -= required_physnets.intersection(
|
||||
host_cell.network_metadata.physnets)
|
||||
|
||||
# however, if we still require some level of NUMA affinity, we need
|
||||
# to make sure one of the other NUMA cells isn't providing that; note
|
||||
# that NUMA affinity might not be provided for all physnets so we are
|
||||
# in effect skipping these
|
||||
for host_cell in host_topology.cells:
|
||||
if 'network_metadata' not in host_cell:
|
||||
continue
|
||||
|
||||
# if one of these cells provides affinity for one or more physnets,
|
||||
# we need to fail because we should be using that node and are not
|
||||
if required_physnets.intersection(
|
||||
host_cell.network_metadata.physnets):
|
||||
return False
|
||||
|
||||
if required_tunnel:
|
||||
# identify if tunneled networks have an affinity to any of our chosen
|
||||
# host NUMA cells
|
||||
for host_cell in chosen_host_cells:
|
||||
if 'network_metadata' not in host_cell:
|
||||
continue
|
||||
|
||||
if host_cell.network_metadata.tunneled:
|
||||
return True
|
||||
|
||||
# however, if we still require some level of NUMA affinity, we need to
|
||||
# make sure one of the other NUMA cells isn't providing that; note
|
||||
# that, as with physnets, NUMA affinity might not be defined for
|
||||
# tunneled networks and we'll simply continue if this is the case
|
||||
for host_cell in host_topology.cells:
|
||||
if 'network_metadata' not in host_cell:
|
||||
continue
|
||||
|
||||
if host_cell.network_metadata.tunneled:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def numa_fit_instance_to_host(
|
||||
host_topology, instance_topology, limits=None,
|
||||
pci_requests=None, pci_stats=None):
|
||||
|
@ -1573,6 +1653,10 @@ def numa_fit_instance_to_host(
|
|||
if 'emulator_threads_policy' in instance_topology:
|
||||
emulator_threads_policy = instance_topology.emulator_threads_policy
|
||||
|
||||
network_metadata = None
|
||||
if limits and 'network_metadata' in limits:
|
||||
network_metadata = limits.network_metadata
|
||||
|
||||
host_cells = host_topology.cells
|
||||
|
||||
# If PCI device(s) are not required, prefer host cells that don't have
|
||||
|
@ -1586,13 +1670,14 @@ def numa_fit_instance_to_host(
|
|||
# depending on whether we want packing/spreading over NUMA nodes
|
||||
for host_cell_perm in itertools.permutations(
|
||||
host_cells, len(instance_topology)):
|
||||
cells = []
|
||||
chosen_instance_cells = []
|
||||
chosen_host_cells = []
|
||||
for host_cell, instance_cell in zip(
|
||||
host_cell_perm, instance_topology.cells):
|
||||
try:
|
||||
cpuset_reserved = 0
|
||||
if (instance_topology.emulator_threads_isolated
|
||||
and len(cells) == 0):
|
||||
and len(chosen_instance_cells) == 0):
|
||||
# For the case of isolate emulator threads, to
|
||||
# make predictable where that CPU overhead is
|
||||
# located we always configure it to be on host
|
||||
|
@ -1608,16 +1693,23 @@ def numa_fit_instance_to_host(
|
|||
break
|
||||
if got_cell is None:
|
||||
break
|
||||
cells.append(got_cell)
|
||||
chosen_host_cells.append(host_cell)
|
||||
chosen_instance_cells.append(got_cell)
|
||||
|
||||
if len(cells) != len(host_cell_perm):
|
||||
if len(chosen_instance_cells) != len(host_cell_perm):
|
||||
continue
|
||||
|
||||
if not pci_requests or ((pci_stats is not None) and
|
||||
pci_stats.support_requests(pci_requests, cells)):
|
||||
return objects.InstanceNUMATopology(
|
||||
cells=cells,
|
||||
emulator_threads_policy=emulator_threads_policy)
|
||||
if pci_requests and pci_stats and not pci_stats.support_requests(
|
||||
pci_requests, chosen_instance_cells):
|
||||
continue
|
||||
|
||||
if network_metadata and not _numa_cells_support_network_metadata(
|
||||
host_topology, chosen_host_cells, network_metadata):
|
||||
continue
|
||||
|
||||
return objects.InstanceNUMATopology(
|
||||
cells=chosen_instance_cells,
|
||||
emulator_threads_policy=emulator_threads_policy)
|
||||
|
||||
|
||||
def numa_get_reserved_huge_pages():
|
||||
|
|
Loading…
Reference in New Issue