From 47d8aa5e7fe0c82ed00017aa2185de1ed5e51a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Antal?= Date: Tue, 5 Jul 2016 00:13:25 +0200 Subject: [PATCH] Throw exception if numa_nodes is not set to integer greater than 0 As [1] is abandoned, I used that patchset to create a new one. This patchset is freshened to the current master branch. This patch introduces InvalidNUMANodesNumber exception, which is thrown when trying to boot an instance with a flavor that has hw:numa_nodes=0 extra spec set. That means that NUMA nodes is set to 0, which is incorrect. [1]: https://review.openstack.org/#/c/190267 Change-Id: I6bd8f69e582c537a5fec40064638a8887a08cac4 Co-Authored-By: Karim Boumedhel Closes-Bug: #1402709 --- nova/api/openstack/compute/servers.py | 1 + nova/exception.py | 5 ++++ .../api/openstack/compute/test_serversV21.py | 8 +++++ nova/tests/unit/virt/test_hardware.py | 30 +++++++++++++++++++ nova/virt/hardware.py | 16 ++++++++++ 5 files changed, 60 insertions(+) diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index fe249ad48157..6df249e9a018 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -689,6 +689,7 @@ class ServersController(wsgi.Controller): exception.ImageNUMATopologyCPUDuplicates, exception.ImageNUMATopologyCPUsUnassigned, exception.ImageNUMATopologyMemoryOutOfRange, + exception.InvalidNUMANodesNumber, exception.InstanceGroupNotFound, exception.PciRequestAliasNotDefined, exception.SnapshotNotFound, diff --git a/nova/exception.py b/nova/exception.py index b0dcbeff4024..c9e52120557d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -372,6 +372,11 @@ class InvalidStrTime(Invalid): msg_fmt = _("Invalid datetime string: %(reason)s") +class InvalidNUMANodesNumber(Invalid): + msg_fmt = _("The property 'numa_nodes' cannot be '%(nodes)s'. " + "It must be a number greater than 0") + + class InvalidName(Invalid): msg_fmt = _("An invalid 'name' value was provided. " "The name must be: %(reason)s") diff --git a/nova/tests/unit/api/openstack/compute/test_serversV21.py b/nova/tests/unit/api/openstack/compute/test_serversV21.py index 5b41393a218d..680a2d2828d6 100644 --- a/nova/tests/unit/api/openstack/compute/test_serversV21.py +++ b/nova/tests/unit/api/openstack/compute/test_serversV21.py @@ -3255,6 +3255,14 @@ class ServersControllerCreateTest(test.TestCase): self.controller.create, self.req, body=self.body) + @mock.patch.object(compute_api.API, 'create', + side_effect=exception.InvalidNUMANodesNumber( + details='')) + def test_create_instance_raise_invalid_numa_nodes(self, mock_create): + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.create, + self.req, body=self.body) + @mock.patch.object(compute_api.API, 'create', side_effect=exception.InvalidBDMFormat(details='')) def test_create_instance_raise_invalid_bdm_format(self, mock_create): diff --git a/nova/tests/unit/virt/test_hardware.py b/nova/tests/unit/virt/test_hardware.py index 8f75879af264..336a48ac5735 100644 --- a/nova/tests/unit/virt/test_hardware.py +++ b/nova/tests/unit/virt/test_hardware.py @@ -856,6 +856,36 @@ class NUMATopologyTest(test.NoDBTestCase): memory=2048, pagesize=2048) ]), }, + { + # a nodes number of zero should lead to an + # exception + "flavor": objects.Flavor(vcpus=8, memory_mb=2048, extra_specs={ + "hw:numa_nodes": 0 + }), + "image": { + }, + "expect": exception.InvalidNUMANodesNumber, + }, + { + # a negative nodes number should lead to an + # exception + "flavor": objects.Flavor(vcpus=8, memory_mb=2048, extra_specs={ + "hw:numa_nodes": -1 + }), + "image": { + }, + "expect": exception.InvalidNUMANodesNumber, + }, + { + # a nodes number not numeric should lead to an + # exception + "flavor": objects.Flavor(vcpus=8, memory_mb=2048, extra_specs={ + "hw:numa_nodes": 'x' + }), + "image": { + }, + "expect": exception.InvalidNUMANodesNumber, + }, { # vcpus is not a multiple of nodes, so it # is an error to not provide cpu/mem mapping diff --git a/nova/virt/hardware.py b/nova/virt/hardware.py index d8eff008ad5d..558bb5b4135e 100644 --- a/nova/virt/hardware.py +++ b/nova/virt/hardware.py @@ -1178,6 +1178,18 @@ def _add_cpu_pinning_constraint(flavor, image_meta, numa_topology): return numa_topology +def _validate_numa_nodes(nodes): + """Validate NUMA nodes number + + :param nodes: The number of NUMA nodes + :raises: exception.InvalidNUMANodesNumber if the given + parameter is not a number or less than 1 + """ + if nodes is not None and (not strutils.is_int_like(nodes) or + int(nodes) < 1): + raise exception.InvalidNUMANodesNumber(nodes=nodes) + + # TODO(sahid): Move numa related to hardward/numa.py def numa_get_constraints(flavor, image_meta): """Return topology related to input request @@ -1189,6 +1201,8 @@ def numa_get_constraints(flavor, image_meta): image properties are not correctly specified, or exception.ImageNUMATopologyForbidden if an attempt is made to override flavor settings with image properties. + exception.InvalidNUMANodesNumber if the number of NUMA + nodes is less than 1 (or not an integer). :returns: InstanceNUMATopology or None """ @@ -1196,12 +1210,14 @@ def numa_get_constraints(flavor, image_meta): nodes = flavor.get('extra_specs', {}).get("hw:numa_nodes") props = image_meta.properties if nodes is not None: + _validate_numa_nodes(nodes) if props.obj_attr_is_set("hw_numa_nodes"): raise exception.ImageNUMATopologyForbidden( name='hw_numa_nodes') nodes = int(nodes) else: nodes = props.get("hw_numa_nodes") + _validate_numa_nodes(nodes) pagesize = _numa_get_pagesize_constraints( flavor, image_meta)