Merge "API: Check for reserved words when naming a node"

This commit is contained in:
Jenkins 2016-05-06 21:14:37 +00:00 committed by Gerrit Code Review
commit 9996949e8e
5 changed files with 69 additions and 3 deletions

View File

@ -1032,11 +1032,21 @@ class NodesController(rest.RestController):
""" """
if not api_utils.allow_node_logical_names(): if not api_utils.allow_node_logical_names():
raise exception.NotAcceptable() raise exception.NotAcceptable()
reserved_names = api_utils.get_controller_reserved_names(
NodesController)
for name in names: for name in names:
if not api_utils.is_valid_node_name(name): if not api_utils.is_valid_node_name(name):
raise wsme.exc.ClientSideError( raise wsme.exc.ClientSideError(
error_msg % {'name': name}, error_msg % {'name': name},
status_code=http_client.BAD_REQUEST) status_code=http_client.BAD_REQUEST)
if name in reserved_names:
raise wsme.exc.ClientSideError(
'The word "%(name)s" is reserved and can not be used as a '
'node name. Other reserved words are: %(reserved)s' %
{'name': name,
'reserved': ', '.join(reserved_names)},
status_code=http_client.BAD_REQUEST)
def _update_changed_fields(self, node, rpc_node): def _update_changed_fields(self, node, rpc_node):
"""Update rpc_node based on changed fields in a node. """Update rpc_node based on changed fields in a node.

View File

@ -13,10 +13,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import inspect
import jsonpatch import jsonpatch
from oslo_config import cfg from oslo_config import cfg
from oslo_utils import uuidutils from oslo_utils import uuidutils
import pecan import pecan
from pecan import rest
import six import six
from six.moves import http_client from six.moves import http_client
from webob.static import FileIter from webob.static import FileIter
@ -284,3 +287,23 @@ def allow_links_node_states_and_driver_properties():
""" """
return (pecan.request.version.minor >= return (pecan.request.version.minor >=
versions.MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES) versions.MINOR_14_LINKS_NODESTATES_DRIVERPROPERTIES)
def get_controller_reserved_names(cls):
"""Get reserved names for a given controller.
Inspect the controller class and return the reserved names within
it. Reserved names are names that can not be used as an identifier
for a resource because the names are either being used as a custom
action or is the name of a nested controller inside the given class.
:param cls: The controller class to be inspected.
"""
reserved_names = [
name for name, member in inspect.getmembers(cls)
if isinstance(member, rest.RestController)]
if hasattr(cls, '_custom_actions'):
reserved_names += cls._custom_actions.keys()
return reserved_names

View File

@ -1256,19 +1256,27 @@ class TestPatch(test_api_base.BaseApiTest):
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.OK, response.status_code) self.assertEqual(http_client.OK, response.status_code)
def test_patch_add_name_invalid(self): def _patch_add_name_invalid_or_reserved(self, name):
self.mock_update_node.return_value = self.node_no_name self.mock_update_node.return_value = self.node_no_name
test_name = 'i am invalid'
response = self.patch_json('/nodes/%s' % self.node_no_name.uuid, response = self.patch_json('/nodes/%s' % self.node_no_name.uuid,
[{'path': '/name', [{'path': '/name',
'op': 'add', 'op': 'add',
'value': test_name}], 'value': name}],
headers={api_base.Version.string: "1.10"}, headers={api_base.Version.string: "1.10"},
expect_errors=True) expect_errors=True)
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.BAD_REQUEST, response.status_code) self.assertEqual(http_client.BAD_REQUEST, response.status_code)
self.assertTrue(response.json['error_message']) self.assertTrue(response.json['error_message'])
def test_patch_add_name_invalid(self):
self._patch_add_name_invalid_or_reserved('i am invalid')
def test_patch_add_name_reserved(self):
reserved_names = api_utils.get_controller_reserved_names(
api_node.NodesController)
for name in reserved_names:
self._patch_add_name_invalid_or_reserved(name)
def test_patch_add_name_empty_invalid(self): def test_patch_add_name_empty_invalid(self):
test_name = '' test_name = ''
response = self.patch_json('/nodes/%s' % self.node_no_name.uuid, response = self.patch_json('/nodes/%s' % self.node_no_name.uuid,
@ -1450,6 +1458,18 @@ class TestPost(test_api_base.BaseApiTest):
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message']) self.assertTrue(response.json['error_message'])
def test_create_node_reserved_name(self):
reserved_names = api_utils.get_controller_reserved_names(
api_node.NodesController)
for name in reserved_names:
ndict = test_api_utils.post_get_test_node(name=name)
response = self.post_json(
'/nodes', ndict, headers={api_base.Version.string: "1.10"},
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['error_message'])
def test_create_node_default_state_none(self): def test_create_node_default_state_none(self):
ndict = test_api_utils.post_get_test_node() ndict = test_api_utils.post_get_test_node()
response = self.post_json('/nodes', ndict, response = self.post_json('/nodes', ndict,

View File

@ -22,6 +22,7 @@ from six.moves import http_client
from webob.static import FileIter from webob.static import FileIter
import wsme import wsme
from ironic.api.controllers.v1 import node as api_node
from ironic.api.controllers.v1 import utils from ironic.api.controllers.v1 import utils
from ironic.common import exception from ironic.common import exception
from ironic import objects from ironic import objects
@ -356,3 +357,10 @@ class TestVendorPassthru(base.TestCase):
def test_vendor_passthru_attach_byte_to_byte(self): def test_vendor_passthru_attach_byte_to_byte(self):
self._test_vendor_passthru_attach(b'\x00\x01', b'\x00\x01') self._test_vendor_passthru_attach(b'\x00\x01', b'\x00\x01')
def test_get_controller_reserved_names(self):
expected = ['maintenance', 'management', 'ports', 'states',
'vendor_passthru', 'validate', 'detail']
self.assertEqual(sorted(expected),
sorted(utils.get_controller_reserved_names(
api_node.NodesController)))

View File

@ -0,0 +1,5 @@
---
fixes:
- Fixes a problem which allowed nodes to be named with some reserved
words that are implicitly not allowed due the way the Ironic API
works.