diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-12-04 05:54:37 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-12-04 05:54:37 +0000 |
commit | a4c57f9c3a45296fab604f48e15e466ee58dee2f (patch) | |
tree | 7a0e1db53a2be47ea47f6311435d5ddaca5c9a1d | |
parent | 0cc4e4587175b0073938768f2ff2dcea89ce0102 (diff) | |
parent | 27d071f44f080d50ac291de2cb9385934b400ccd (diff) | |
download | nova-a4c57f9c3a45296fab604f48e15e466ee58dee2f.tar.gz |
Merge "Add support for fitting instance NUMA nodes onto a host" into stable/juno
-rw-r--r-- | nova/tests/virt/test_hardware.py | 134 | ||||
-rw-r--r-- | nova/virt/hardware.py | 71 |
2 files changed, 205 insertions, 0 deletions
diff --git a/nova/tests/virt/test_hardware.py b/nova/tests/virt/test_hardware.py index 8767a8b163..479e7e41b9 100644 --- a/nova/tests/virt/test_hardware.py +++ b/nova/tests/virt/test_hardware.py @@ -1141,6 +1141,140 @@ class NUMATopologyTest(test.NoDBTestCase): self.assertNUMACellMatches(exp_cell, got_cell) +class VirtNUMATopologyCellUsageTestCase(test.NoDBTestCase): + def test_fit_instance_cell_success_no_limit(self): + host_cell = hw.VirtNUMATopologyCellUsage(4, set([1, 2]), 1024) + instance_cell = hw.VirtNUMATopologyCell( + None, set([1, 2]), 1024) + fitted_cell = host_cell.fit_instance_cell(host_cell, instance_cell) + self.assertIsInstance(fitted_cell, hw.VirtNUMATopologyCell) + self.assertEqual(host_cell.id, fitted_cell.id) + + def test_fit_instance_cell_success_w_limit(self): + host_cell = hw.VirtNUMATopologyCellUsage(4, set([1, 2]), 1024, + cpu_usage=2, + memory_usage=1024) + limit_cell = hw.VirtNUMATopologyCellLimit( + 4, set([1, 2]), 1024, + cpu_limit=4, memory_limit=2048) + instance_cell = hw.VirtNUMATopologyCell( + None, set([1, 2]), 1024) + fitted_cell = host_cell.fit_instance_cell( + host_cell, instance_cell, limit_cell=limit_cell) + self.assertIsInstance(fitted_cell, hw.VirtNUMATopologyCell) + self.assertEqual(host_cell.id, fitted_cell.id) + + def test_fit_instance_cell_self_overcommit(self): + host_cell = hw.VirtNUMATopologyCellUsage(4, set([1, 2]), 1024) + limit_cell = hw.VirtNUMATopologyCellLimit( + 4, set([1, 2]), 1024, + cpu_limit=4, memory_limit=2048) + instance_cell = hw.VirtNUMATopologyCell( + None, set([1, 2, 3]), 4096) + fitted_cell = host_cell.fit_instance_cell( + host_cell, instance_cell, limit_cell=limit_cell) + self.assertIsNone(fitted_cell) + + def test_fit_instance_cell_fail_w_limit(self): + host_cell = hw.VirtNUMATopologyCellUsage(4, set([1, 2]), 1024, + cpu_usage=2, + memory_usage=1024) + limit_cell = hw.VirtNUMATopologyCellLimit( + 4, set([1, 2]), 1024, + cpu_limit=4, memory_limit=2048) + instance_cell = hw.VirtNUMATopologyCell( + None, set([1, 2]), 4096) + fitted_cell = host_cell.fit_instance_cell( + host_cell, instance_cell, limit_cell=limit_cell) + self.assertIsNone(fitted_cell) + + instance_cell = hw.VirtNUMATopologyCell( + None, set([1, 2, 3, 4, 5]), 1024) + fitted_cell = host_cell.fit_instance_cell( + host_cell, instance_cell, limit_cell=limit_cell) + self.assertIsNone(fitted_cell) + + +class VirtNUMAHostTopologyTestCase(test.NoDBTestCase): + def setUp(self): + super(VirtNUMAHostTopologyTestCase, self).setUp() + + self.host = hw.VirtNUMAHostTopology( + cells=[ + hw.VirtNUMATopologyCellUsage( + 1, set([1, 2]), 2048, + cpu_usage=2, memory_usage=2048), + hw.VirtNUMATopologyCellUsage( + 2, set([3, 4]), 2048, + cpu_usage=2, memory_usage=2048)]) + + self.limits = hw.VirtNUMALimitTopology( + cells=[ + hw.VirtNUMATopologyCellLimit( + 1, set([1, 2]), 2048, + cpu_limit=4, memory_limit=4096), + hw.VirtNUMATopologyCellLimit( + 2, set([3, 4]), 2048, + cpu_limit=4, memory_limit=3072)]) + + self.instance1 = hw.VirtNUMAInstanceTopology( + cells=[ + hw.VirtNUMATopologyCell( + None, set([1, 2]), 2048)]) + self.instance2 = hw.VirtNUMAInstanceTopology( + cells=[ + hw.VirtNUMATopologyCell( + None, set([1, 2, 3, 4]), 1024)]) + self.instance3 = hw.VirtNUMAInstanceTopology( + cells=[ + hw.VirtNUMATopologyCell( + None, set([1, 2]), 1024)]) + + def test_get_fitting_success_no_limits(self): + fitted_instance1 = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance1) + self.assertIsInstance(fitted_instance1, hw.VirtNUMAInstanceTopology) + self.host = hw.VirtNUMAHostTopology.usage_from_instances(self.host, + [fitted_instance1]) + fitted_instance2 = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance3) + self.assertIsInstance(fitted_instance2, hw.VirtNUMAInstanceTopology) + + def test_get_fitting_success_limits(self): + fitted_instance = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance3, self.limits) + self.assertIsInstance(fitted_instance, hw.VirtNUMAInstanceTopology) + self.assertEqual(1, fitted_instance.cells[0].id) + + def test_get_fitting_fails_no_limits(self): + fitted_instance = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance2, self.limits) + self.assertIsNone(fitted_instance) + + def test_get_fitting_culmulative_fails_limits(self): + fitted_instance1 = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance1, self.limits) + self.assertIsInstance(fitted_instance1, hw.VirtNUMAInstanceTopology) + self.assertEqual(1, fitted_instance1.cells[0].id) + self.host = hw.VirtNUMAHostTopology.usage_from_instances(self.host, + [fitted_instance1]) + fitted_instance2 = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance1, self.limits) + self.assertIsNone(fitted_instance2) + + def test_get_fitting_culmulative_success_limits(self): + fitted_instance1 = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance1, self.limits) + self.assertIsInstance(fitted_instance1, hw.VirtNUMAInstanceTopology) + self.assertEqual(1, fitted_instance1.cells[0].id) + self.host = hw.VirtNUMAHostTopology.usage_from_instances(self.host, + [fitted_instance1]) + fitted_instance2 = hw.VirtNUMAHostTopology.fit_instance_to_host( + self.host, self.instance3, self.limits) + self.assertIsInstance(fitted_instance2, hw.VirtNUMAInstanceTopology) + self.assertEqual(2, fitted_instance2.cells[0].id) + + class NumberOfSerialPortsTest(test.NoDBTestCase): def test_flavor(self): flavor = FakeFlavorObject(8, 2048, {"hw:serial_port_count": 3}) diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index 8f2b24f179..a5a167439b 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -13,6 +13,7 @@ # under the License. import collections +import itertools from oslo.config import cfg import six @@ -648,6 +649,35 @@ class VirtNUMATopologyCellUsage(VirtNUMATopologyCell): self.cpu_usage = cpu_usage self.memory_usage = memory_usage + @classmethod + def fit_instance_cell(cls, host_cell, instance_cell, limit_cell=None): + """Check if a instance cell can fit and set it's cell id + + :param host_cell: host cell to fit the instance cell onto + :param instance_cell: instance cell we want to fit + :param limit_cell: cell with limits of the host_cell if any + + Make sure we can fit the instance cell onto a host cell and if so, + return a new VirtNUMATopologyCell with the id set to that of + the host, or None if the cell exceeds the limits of the host + + :returns: a new instance cell or None + """ + # NOTE (ndipanov): do not allow an instance to overcommit against + # itself on any NUMA cell + if (instance_cell.memory > host_cell.memory or + len(instance_cell.cpuset) > len(host_cell.cpuset)): + return None + + if limit_cell: + memory_usage = host_cell.memory_usage + instance_cell.memory + cpu_usage = host_cell.cpu_usage + len(instance_cell.cpuset) + if (memory_usage > limit_cell.memory_limit or + cpu_usage > limit_cell.cpu_limit): + return None + return VirtNUMATopologyCell( + host_cell.id, instance_cell.cpuset, instance_cell.memory) + def _to_dict(self): data_dict = super(VirtNUMATopologyCellUsage, self)._to_dict() data_dict['mem']['used'] = self.memory_usage @@ -861,6 +891,47 @@ class VirtNUMAHostTopology(VirtNUMATopology): for instance_cells in instances_cells) @classmethod + def fit_instance_to_host(cls, host_topology, instance_topology, + limits_topology=None): + """Fit the instance topology onto the host topology given the limits + + :param host_topology: VirtNUMAHostTopology object to fit an instance on + :param instance_topology: VirtNUMAInstanceTopology object to be fitted + :param limits_topology: VirtNUMALimitTopology that defines limits + + Given a host and instance topology and optionally limits - this method + will attempt to fit instance cells onto all permutations of host cells + by calling the fit_instance_cell method, and return a new + VirtNUMAInstanceTopology with it's cell ids set to host cell id's of + the first successful permutation, or None. + """ + if (not (host_topology and instance_topology) or + len(host_topology) < len(instance_topology)): + return + else: + if limits_topology is None: + limits_topology_cells = itertools.repeat( + None, len(host_topology)) + else: + limits_topology_cells = limits_topology.cells + # 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( + zip(host_topology.cells, limits_topology_cells), + len(instance_topology) + ): + cells = [] + for (host_cell, limit_cell), instance_cell in zip( + host_cell_perm, instance_topology.cells): + got_cell = cls.cell_class.fit_instance_cell( + host_cell, instance_cell, limit_cell) + if got_cell is None: + break + cells.append(got_cell) + if len(cells) == len(host_cell_perm): + return VirtNUMAInstanceTopology(cells=cells) + + @classmethod def usage_from_instances(cls, host, instances, free=False): """Get host topology usage |