Add possibility to remove chassis_uuid from a node

Allow to unset the field "chassis_uuid" from a node using the "ironic
node-update <node_uuid> remove chassis_uuid" command.
The API version has been bumped to 1.25.

Change-Id: I1c8406f83f9d240ede99b0458c5e8b6967f2e37a
Closes-Bug: #1563263
This commit is contained in:
Aline Bousquet 2016-10-25 14:13:05 +01:00
parent dd57ed5a2d
commit 1f61654019
8 changed files with 76 additions and 4 deletions

View File

@ -326,6 +326,8 @@ Updates the information stored about a Node.
Note that this endpoint can not be used to request state changes, which are Note that this endpoint can not be used to request state changes, which are
managed through sub-resources. managed through sub-resources.
API microversion 1.25 introduced the ability to unset a node's chassis UUID.
Normal response codes: 200 Normal response codes: 200
.. TODO: add error codes .. TODO: add error codes

View File

@ -2,6 +2,10 @@
REST API Version History REST API Version History
======================== ========================
**1.25**
Add possibility to unset chassis_uuid from a node.
**1.24** **1.24**
Added new endpoints '/v1/nodes/<node>/portgroups' and '/v1/portgroups/<portgroup>/ports'. Added new endpoints '/v1/nodes/<node>/portgroups' and '/v1/portgroups/<portgroup>/ports'.

View File

@ -893,8 +893,6 @@ class NodePatchType(types.JsonPatchType):
_api_base = Node _api_base = Node
_extra_non_removable_attrs = {'/chassis_uuid'}
@staticmethod @staticmethod
def internal_attrs(): def internal_attrs():
defaults = types.JsonPatchType.internal_attrs() defaults = types.JsonPatchType.internal_attrs()
@ -1192,7 +1190,17 @@ class NodesController(rest.RestController):
try: try:
patch_val = getattr(node, field) patch_val = getattr(node, field)
except AttributeError: except AttributeError:
# Ignore fields that aren't exposed in the API # Ignore fields that aren't exposed in the API, except
# chassis_id. chassis_id would have been set (instead of
# chassis_uuid) if the node belongs to a chassis. This
# AttributeError is raised for chassis_id only if
# 1. the node doesn't belong to a chassis or
# 2. the node belonged to a chassis but is now being removed
# from the chassis.
if (field == "chassis_id" and rpc_node[field] is not None):
if not api_utils.allow_remove_chassis_uuid():
raise exception.NotAcceptable()
rpc_node[field] = None
continue continue
if patch_val == wtypes.Unset: if patch_val == wtypes.Unset:
patch_val = None patch_val = None

View File

@ -432,6 +432,16 @@ def allow_portgroups_subcontrollers():
versions.MINOR_24_PORTGROUPS_SUBCONTROLLERS) versions.MINOR_24_PORTGROUPS_SUBCONTROLLERS)
def allow_remove_chassis_uuid():
"""Check if chassis_uuid can be removed from node.
Version 1.25 of the API added support for chassis_uuid
removal
"""
return (pecan.request.version.minor >=
versions.MINOR_25_UNSET_CHASSIS_UUID)
def get_controller_reserved_names(cls): def get_controller_reserved_names(cls):
"""Get reserved names for a given controller. """Get reserved names for a given controller.

View File

@ -55,6 +55,7 @@ BASE_VERSION = 1
# v1.23: Add portgroup support. # v1.23: Add portgroup support.
# v1.24: Add subcontrollers: node.portgroup, portgroup.ports. # v1.24: Add subcontrollers: node.portgroup, portgroup.ports.
# Add port.portgroup_uuid field. # Add port.portgroup_uuid field.
# v1.25: Add possibility to unset chassis_uuid from node.
MINOR_0_JUNO = 0 MINOR_0_JUNO = 0
MINOR_1_INITIAL_VERSION = 1 MINOR_1_INITIAL_VERSION = 1
@ -81,11 +82,12 @@ MINOR_21_RESOURCE_CLASS = 21
MINOR_22_LOOKUP_HEARTBEAT = 22 MINOR_22_LOOKUP_HEARTBEAT = 22
MINOR_23_PORTGROUPS = 23 MINOR_23_PORTGROUPS = 23
MINOR_24_PORTGROUPS_SUBCONTROLLERS = 24 MINOR_24_PORTGROUPS_SUBCONTROLLERS = 24
MINOR_25_UNSET_CHASSIS_UUID = 25
# When adding another version, update MINOR_MAX_VERSION and also update # When adding another version, update MINOR_MAX_VERSION and also update
# doc/source/dev/webapi-version-history.rst with a detailed explanation of # doc/source/dev/webapi-version-history.rst with a detailed explanation of
# what the version has changed. # what the version has changed.
MINOR_MAX_VERSION = MINOR_24_PORTGROUPS_SUBCONTROLLERS MINOR_MAX_VERSION = MINOR_25_UNSET_CHASSIS_UUID
# String representations of the minor and maximum versions # String representations of the minor and maximum versions
MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)

View File

@ -32,6 +32,7 @@ from ironic.api.controllers import base as api_base
from ironic.api.controllers import v1 as api_v1 from ironic.api.controllers import v1 as api_v1
from ironic.api.controllers.v1 import node as api_node from ironic.api.controllers.v1 import node as api_node
from ironic.api.controllers.v1 import utils as api_utils from ironic.api.controllers.v1 import utils as api_utils
from ironic.api.controllers.v1 import versions
from ironic.common import boot_devices from ironic.common import boot_devices
from ironic.common import exception from ironic.common import exception
from ironic.common import states from ironic.common import states
@ -1317,6 +1318,40 @@ 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_remove_chassis_uuid(self):
self.mock_update_node.return_value = self.node
headers = {api_base.Version.string: "1.25"}
response = self.patch_json('/nodes/%s' % self.node.uuid,
[{'path': '/chassis_uuid',
'op': 'remove'}],
headers=headers)
self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.OK, response.status_code)
def test_remove_chassis_uuid_invalid_api_version(self):
self.mock_update_node.return_value = self.node
headers = {api_base.Version.string: "1.24"}
response = self.patch_json('/nodes/%s' % self.node.uuid,
[{'path': '/chassis_uuid',
'op': 'remove'}],
headers=headers,
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code)
self.assertTrue(response.json['error_message'])
@mock.patch("pecan.request")
def test__update_changed_fields_remove_chassis_uuid(self, mock_pecan_req):
mock_pecan_req.version.minor = versions.MINOR_MAX_VERSION
controller = api_node.NodesController()
node_dict = self.node.as_dict()
del node_dict['chassis_id']
node_no_chassis = api_node.Node(**node_dict)
controller._update_changed_fields(node_no_chassis, self.node)
self.assertIsNone(self.node.chassis_id)
def test_add_chassis_id(self): def test_add_chassis_id(self):
response = self.patch_json('/nodes/%s' % self.node.uuid, response = self.patch_json('/nodes/%s' % self.node.uuid,
[{'path': '/chassis_id', [{'path': '/chassis_id',

View File

@ -299,6 +299,13 @@ class TestApiUtils(base.TestCase):
mock_request.version.minor = 22 mock_request.version.minor = 22
self.assertFalse(utils.allow_portgroups()) self.assertFalse(utils.allow_portgroups())
@mock.patch.object(pecan, 'request', spec_set=['version'])
def test_allow_remove_chassis_uuid(self, mock_request):
mock_request.version.minor = 25
self.assertTrue(utils.allow_remove_chassis_uuid())
mock_request.version.minor = 24
self.assertFalse(utils.allow_remove_chassis_uuid())
class TestNodeIdent(base.TestCase): class TestNodeIdent(base.TestCase):

View File

@ -0,0 +1,4 @@
---
features:
- Adds support for removing the chassis UUID associated with a node (via
PATCH /v1/nodes/<ident>). This is available starting with API version 1.25.