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 <karimboumedhel@gmail.com>
Closes-Bug: #1402709
This commit is contained in:
Gábor Antal 2016-07-05 00:13:25 +02:00
parent b61cb28e98
commit 47d8aa5e7f
5 changed files with 60 additions and 0 deletions

View File

@ -689,6 +689,7 @@ class ServersController(wsgi.Controller):
exception.ImageNUMATopologyCPUDuplicates, exception.ImageNUMATopologyCPUDuplicates,
exception.ImageNUMATopologyCPUsUnassigned, exception.ImageNUMATopologyCPUsUnassigned,
exception.ImageNUMATopologyMemoryOutOfRange, exception.ImageNUMATopologyMemoryOutOfRange,
exception.InvalidNUMANodesNumber,
exception.InstanceGroupNotFound, exception.InstanceGroupNotFound,
exception.PciRequestAliasNotDefined, exception.PciRequestAliasNotDefined,
exception.SnapshotNotFound, exception.SnapshotNotFound,

View File

@ -372,6 +372,11 @@ class InvalidStrTime(Invalid):
msg_fmt = _("Invalid datetime string: %(reason)s") 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): class InvalidName(Invalid):
msg_fmt = _("An invalid 'name' value was provided. " msg_fmt = _("An invalid 'name' value was provided. "
"The name must be: %(reason)s") "The name must be: %(reason)s")

View File

@ -3255,6 +3255,14 @@ class ServersControllerCreateTest(test.TestCase):
self.controller.create, self.controller.create,
self.req, body=self.body) 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', @mock.patch.object(compute_api.API, 'create',
side_effect=exception.InvalidBDMFormat(details='')) side_effect=exception.InvalidBDMFormat(details=''))
def test_create_instance_raise_invalid_bdm_format(self, mock_create): def test_create_instance_raise_invalid_bdm_format(self, mock_create):

View File

@ -856,6 +856,36 @@ class NUMATopologyTest(test.NoDBTestCase):
memory=2048, pagesize=2048) 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 # vcpus is not a multiple of nodes, so it
# is an error to not provide cpu/mem mapping # is an error to not provide cpu/mem mapping

View File

@ -1178,6 +1178,18 @@ def _add_cpu_pinning_constraint(flavor, image_meta, numa_topology):
return 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 # TODO(sahid): Move numa related to hardward/numa.py
def numa_get_constraints(flavor, image_meta): def numa_get_constraints(flavor, image_meta):
"""Return topology related to input request """Return topology related to input request
@ -1189,6 +1201,8 @@ def numa_get_constraints(flavor, image_meta):
image properties are not correctly specified, or image properties are not correctly specified, or
exception.ImageNUMATopologyForbidden if an attempt is exception.ImageNUMATopologyForbidden if an attempt is
made to override flavor settings with image properties. 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 :returns: InstanceNUMATopology or None
""" """
@ -1196,12 +1210,14 @@ def numa_get_constraints(flavor, image_meta):
nodes = flavor.get('extra_specs', {}).get("hw:numa_nodes") nodes = flavor.get('extra_specs', {}).get("hw:numa_nodes")
props = image_meta.properties props = image_meta.properties
if nodes is not None: if nodes is not None:
_validate_numa_nodes(nodes)
if props.obj_attr_is_set("hw_numa_nodes"): if props.obj_attr_is_set("hw_numa_nodes"):
raise exception.ImageNUMATopologyForbidden( raise exception.ImageNUMATopologyForbidden(
name='hw_numa_nodes') name='hw_numa_nodes')
nodes = int(nodes) nodes = int(nodes)
else: else:
nodes = props.get("hw_numa_nodes") nodes = props.get("hw_numa_nodes")
_validate_numa_nodes(nodes)
pagesize = _numa_get_pagesize_constraints( pagesize = _numa_get_pagesize_constraints(
flavor, image_meta) flavor, image_meta)