summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-12-04 05:54:37 +0000
committerGerrit Code Review <review@openstack.org>2014-12-04 05:54:37 +0000
commita4c57f9c3a45296fab604f48e15e466ee58dee2f (patch)
tree7a0e1db53a2be47ea47f6311435d5ddaca5c9a1d
parent0cc4e4587175b0073938768f2ff2dcea89ce0102 (diff)
parent27d071f44f080d50ac291de2cb9385934b400ccd (diff)
downloadnova-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.py134
-rw-r--r--nova/virt/hardware.py71
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