From 5c303a5e0af7c07186986f9c025ec8b594f7c5ed Mon Sep 17 00:00:00 2001 From: kafilat-adeleke Date: Tue, 6 Apr 2021 09:58:20 +0100 Subject: [PATCH] Aliases for a few unfortunately named state transitions This RFE proposes a new microversion that will provide aliases to two poorly named provisioning verbs to match the existing CLI commands Story: #2007551 Task: #39402 Change-Id: Ifd14aebbfb4b17c5108f44092dac0b89d1c2c50a --- .../contributor/webapi-version-history.rst | 5 +++ ironic/api/controllers/v1/node.py | 4 +-- ironic/api/controllers/v1/utils.py | 2 ++ ironic/api/controllers/v1/versions.py | 4 ++- ironic/common/release_mappings.py | 2 +- ironic/common/states.py | 12 +++++++ .../unit/api/controllers/v1/test_node.py | 36 +++++++++++++++++++ .../unit/api/controllers/v1/test_utils.py | 18 ++++++++++ ...ed-state-transitions-a32433ad65638706.yaml | 6 ++++ 9 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-2007551-aliases-for-a-few-named-state-transitions-a32433ad65638706.yaml diff --git a/doc/source/contributor/webapi-version-history.rst b/doc/source/contributor/webapi-version-history.rst index fcb20ba188..37adc74cf1 100644 --- a/doc/source/contributor/webapi-version-history.rst +++ b/doc/source/contributor/webapi-version-history.rst @@ -2,6 +2,11 @@ REST API Version History ======================== +1.73 (Xena) +---------------------- +Add a new ``deploy`` verb as an alias to ``active`` and +``undeploy`` verb as an alias to ``deleted``. + 1.72 (Wallaby, 17.0) ---------------------- diff --git a/ironic/api/controllers/v1/node.py b/ironic/api/controllers/v1/node.py index ba36d261c6..401bd577c6 100644 --- a/ironic/api/controllers/v1/node.py +++ b/ironic/api/controllers/v1/node.py @@ -825,7 +825,7 @@ class NodeStatesController(rest.RestController): # Note that there is a race condition. The node state(s) could change # by the time the RPC call is made and the TaskManager manager gets a # lock. - if target in (ir_states.ACTIVE, ir_states.REBUILD): + if target in (ir_states.ACTIVE, ir_states.REBUILD, ir_states.DEPLOY): rebuild = (target == ir_states.REBUILD) if deploy_steps: _check_deploy_steps(deploy_steps) @@ -847,7 +847,7 @@ class NodeStatesController(rest.RestController): msg, status_code=http_client.BAD_REQUEST) api.request.rpcapi.do_node_rescue( api.request.context, rpc_node.uuid, rescue_password, topic) - elif target == ir_states.DELETED: + elif target in (ir_states.DELETED, ir_states.UNDEPLOY): api.request.rpcapi.do_node_tear_down( api.request.context, rpc_node.uuid, topic) elif target == ir_states.VERBS['inspect']: diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py index 65ce7fda58..83c2a16027 100644 --- a/ironic/api/controllers/v1/utils.py +++ b/ironic/api/controllers/v1/utils.py @@ -67,6 +67,8 @@ MIN_VERB_VERSIONS = { states.VERBS['adopt']: versions.MINOR_17_ADOPT_VERB, states.VERBS['rescue']: versions.MINOR_38_RESCUE_INTERFACE, states.VERBS['unrescue']: versions.MINOR_38_RESCUE_INTERFACE, + states.VERBS['deploy']: versions.MINOR_73_DEPLOY_UNDEPLOY_VERBS, + states.VERBS['undeploy']: versions.MINOR_73_DEPLOY_UNDEPLOY_VERBS, } V31_FIELDS = [ diff --git a/ironic/api/controllers/v1/versions.py b/ironic/api/controllers/v1/versions.py index 12c15fe837..97e03485ed 100644 --- a/ironic/api/controllers/v1/versions.py +++ b/ironic/api/controllers/v1/versions.py @@ -110,6 +110,7 @@ BASE_VERSION = 1 # v1.70: Add disable_ramdisk to manual cleaning. # v1.71: Add signifier for Scope based roles. # v1.72: Add agent_status and agent_status_message to /v1/heartbeat +# v1.73: Add support for deploy and undeploy verbs MINOR_0_JUNO = 0 MINOR_1_INITIAL_VERSION = 1 @@ -184,6 +185,7 @@ MINOR_69_DEPLOY_STEPS = 69 MINOR_70_CLEAN_DISABLE_RAMDISK = 70 MINOR_71_RBAC_SCOPES = 71 MINOR_72_HEARTBEAT_STATUS = 72 +MINOR_73_DEPLOY_UNDEPLOY_VERBS = 73 # When adding another version, update: # - MINOR_MAX_VERSION @@ -191,7 +193,7 @@ MINOR_72_HEARTBEAT_STATUS = 72 # explanation of what changed in the new version # - common/release_mappings.py, RELEASE_MAPPING['master']['api'] -MINOR_MAX_VERSION = MINOR_72_HEARTBEAT_STATUS +MINOR_MAX_VERSION = MINOR_73_DEPLOY_UNDEPLOY_VERBS # String representations of the minor and maximum versions _MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index 37b5f11108..526f424f7b 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -320,7 +320,7 @@ RELEASE_MAPPING = { } }, 'master': { - 'api': '1.72', + 'api': '1.73', 'rpc': '1.54', 'objects': { 'Allocation': ['1.1'], diff --git a/ironic/common/states.py b/ironic/common/states.py index 567ce0eea3..3b0cbe18af 100644 --- a/ironic/common/states.py +++ b/ironic/common/states.py @@ -41,7 +41,9 @@ LOG = logging.getLogger(__name__) # TODO(tenbrae): add add'l state mappings here VERBS = { 'active': 'deploy', + 'deploy': 'deploy', 'deleted': 'delete', + 'undeploy': 'delete', 'manage': 'manage', 'provide': 'provide', 'inspect': 'inspect', @@ -96,6 +98,11 @@ This state is replacing the NOSTATE state used prior to Kilo. ACTIVE = 'active' """ Node is successfully deployed and associated with an instance. """ +DEPLOY = 'deploy' +""" Node is successfully deployed and associated with an instance. +This is an alias for ACTIVE. +""" + DEPLOYWAIT = 'wait call-back' """ Node is waiting to be deployed. @@ -137,6 +144,11 @@ represented in target_provision_state. CLEANING = 'cleaning' """ Node is being automatically cleaned to prepare it for provisioning. """ +UNDEPLOY = 'undeploy' +""" Node tear down process has started. +This is an alias for DELETED. +""" + CLEANWAIT = 'clean wait' """ Node is waiting for a clean step to be finished. diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py index 302da10551..cc778cd770 100644 --- a/ironic/tests/unit/api/controllers/v1/test_node.py +++ b/ironic/tests/unit/api/controllers/v1/test_node.py @@ -5093,6 +5093,25 @@ class TestPut(test_api_base.BaseApiTest): self.assertEqual(urlparse.urlparse(ret.location).path, expected_location) + def test_provision_deploy(self): + ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid, + {'target': states.DEPLOY}, + headers={api_base.Version.string: "1.73"}) + self.assertEqual(http_client.ACCEPTED, ret.status_code) + self.assertEqual(b'', ret.body) + self.mock_dnd.assert_called_once_with(mock.ANY, + context=mock.ANY, + node_id=self.node.uuid, + rebuild=False, + configdrive=None, + topic='test-topic', + deploy_steps=None) + # Check location header + self.assertIsNotNone(ret.location) + expected_location = '/v1/nodes/%s/states' % self.node.uuid + self.assertEqual(urlparse.urlparse(ret.location).path, + expected_location) + def test_provision_by_name_unsupported(self): ret = self.put_json('/nodes/%s/states/provision' % self.node.name, {'target': states.ACTIVE}, @@ -5354,6 +5373,23 @@ ORHMKeXMO8fcK0By7CiMKwHSXCoEQgfQhWwpMdSsO8LgHCjh87DQc= """ self.assertEqual(urlparse.urlparse(ret.location).path, expected_location) + def test_provision_with_tear_down_undeploy(self): + node = self.node + node.provision_state = states.ACTIVE + node.target_provision_state = states.NOSTATE + node.save() + ret = self.put_json('/nodes/%s/states/provision' % node.uuid, + {'target': states.UNDEPLOY}) + self.assertEqual(http_client.ACCEPTED, ret.status_code) + self.assertEqual(b'', ret.body) + self.mock_dntd.assert_called_once_with( + mock.ANY, mock.ANY, node.uuid, 'test-topic') + # Check location header + self.assertIsNotNone(ret.location) + expected_location = '/v1/nodes/%s/states' % node.uuid + self.assertEqual(urlparse.urlparse(ret.location).path, + expected_location) + def test_provision_already_in_progress(self): node = self.node node.provision_state = states.DEPLOYING diff --git a/ironic/tests/unit/api/controllers/v1/test_utils.py b/ironic/tests/unit/api/controllers/v1/test_utils.py index 8c948f255d..d12e4328a5 100644 --- a/ironic/tests/unit/api/controllers/v1/test_utils.py +++ b/ironic/tests/unit/api/controllers/v1/test_utils.py @@ -556,6 +556,24 @@ class TestCheckAllowFields(base.TestCase): self.assertRaises(exception.NotAcceptable, utils.check_allow_management_verbs, 'clean') + def test_check_allow_deploy_verbs(self, mock_request): + mock_request.version.minor = 73 + utils.check_allow_management_verbs('deploy') + + def test_check_allow_deploy_verbs_fail(self, mock_request): + mock_request.version.minor = 72 + self.assertRaises(exception.NotAcceptable, + utils.check_allow_management_verbs, 'deploy') + + def test_check_allow_undeploy_verbs(self, mock_request): + mock_request.version.minor = 73 + utils.check_allow_management_verbs('undeploy') + + def test_check_allow_undeploy_verbs_fail(self, mock_request): + mock_request.version.minor = 72 + self.assertFalse( + utils.check_allow_management_verbs('undeploy')) + def test_check_allow_unknown_verbs(self, mock_request): utils.check_allow_management_verbs('rebuild') diff --git a/releasenotes/notes/bug-2007551-aliases-for-a-few-named-state-transitions-a32433ad65638706.yaml b/releasenotes/notes/bug-2007551-aliases-for-a-few-named-state-transitions-a32433ad65638706.yaml new file mode 100644 index 0000000000..387b8a7ee5 --- /dev/null +++ b/releasenotes/notes/bug-2007551-aliases-for-a-few-named-state-transitions-a32433ad65638706.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds a new ``deploy`` verb as an alias to ``active`` and + ``undeploy`` verb as an alias to ``deleted`` + See `story 2007551 `_. \ No newline at end of file