API: Check for reserved words when naming a node
This patch is introducing a mechanism which prevents nodes to be named with some reserved words that are implicitly not allowed today due the way the Ironic API works. For example, the way we fetch a node by its name in the API is issuing a GET to the http://<address>/v1/nodes/<node name> but, we have some words that are valid endpoints under the same path. One example is of it is the /detail endpoint, a GET request to v1/nodes/detail should return detailed information about all the nodes. Apart from that, due the way pecan/wsme parses/routes the requests the words 'ports', 'vendor_passthru', 'management', 'maintenance' can not be used. These words are attributes pointing to a controller in the node's API object (e.g v1/nodes/(ident)/ports) and naming a node as such confuses pecan/wsme. The API microversion was not bumped because this error should be prevented across all versions. Closes-Bug: #1572651 Change-Id: Ibba5ed16e6961805864bdf46b94296b1b0c09469
This commit is contained in:
parent
75f7831add
commit
8e5e69869d
@ -1032,11 +1032,21 @@ class NodesController(rest.RestController):
|
||||
"""
|
||||
if not api_utils.allow_node_logical_names():
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
reserved_names = api_utils.get_controller_reserved_names(
|
||||
NodesController)
|
||||
for name in names:
|
||||
if not api_utils.is_valid_node_name(name):
|
||||
raise wsme.exc.ClientSideError(
|
||||
error_msg % {'name': name},
|
||||
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):
|
||||
"""Update rpc_node based on changed fields in a node.
|
||||
|
@ -13,10 +13,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import inspect
|
||||
|
||||
import jsonpatch
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import six
|
||||
from six.moves import http_client
|
||||
from webob.static import FileIter
|
||||
@ -284,3 +287,23 @@ def allow_links_node_states_and_driver_properties():
|
||||
"""
|
||||
return (pecan.request.version.minor >=
|
||||
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
|
||||
|
@ -1256,19 +1256,27 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
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
|
||||
test_name = 'i am invalid'
|
||||
response = self.patch_json('/nodes/%s' % self.node_no_name.uuid,
|
||||
[{'path': '/name',
|
||||
'op': 'add',
|
||||
'value': test_name}],
|
||||
'value': name}],
|
||||
headers={api_base.Version.string: "1.10"},
|
||||
expect_errors=True)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_code)
|
||||
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):
|
||||
test_name = ''
|
||||
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.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):
|
||||
ndict = test_api_utils.post_get_test_node()
|
||||
response = self.post_json('/nodes', ndict,
|
||||
|
@ -22,6 +22,7 @@ from six.moves import http_client
|
||||
from webob.static import FileIter
|
||||
import wsme
|
||||
|
||||
from ironic.api.controllers.v1 import node as api_node
|
||||
from ironic.api.controllers.v1 import utils
|
||||
from ironic.common import exception
|
||||
from ironic import objects
|
||||
@ -356,3 +357,10 @@ class TestVendorPassthru(base.TestCase):
|
||||
|
||||
def test_vendor_passthru_attach_byte_to_byte(self):
|
||||
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)))
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user