Merge "hardware: Start accounting for networks in NUMA fitting"

This commit is contained in:
Zuul 2018-07-18 12:24:10 +00:00 committed by Gerrit Code Review
commit 98d09d519f
2 changed files with 252 additions and 9 deletions

View File

@ -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)

View File

@ -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():