From 8df29e00a28b5b619f31355dca8be08ab975333a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aija=20Jaunt=C4=93va?= Date: Mon, 21 Dec 2020 13:29:01 -0500 Subject: [PATCH] Add 'deploy steps' for provisioning API Story: 2008043 Task: 41409 Depends-On: https://review.opendev.org/c/openstack/ironic/+/768353 Change-Id: I6adffcf304ca090ff551280f3ec4c9d09a5537d8 --- ironicclient/common/http.py | 2 +- ironicclient/osc/v1/baremetal_node.py | 28 +++++++++++++++++ .../tests/unit/osc/v1/test_baremetal_node.py | 30 +++++++++++-------- ironicclient/tests/unit/v1/test_node.py | 25 ++++++++++++++++ ironicclient/v1/node.py | 13 ++++++-- ...add-deploy-steps-arg-0b127e29c8cf976d.yaml | 15 ++++++++++ 6 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/add-deploy-steps-arg-0b127e29c8cf976d.yaml diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py index 9f2cd812b..c12a62606 100644 --- a/ironicclient/common/http.py +++ b/ironicclient/common/http.py @@ -37,7 +37,7 @@ from ironicclient import exc # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # for full details. DEFAULT_VER = '1.9' -LAST_KNOWN_API_VERSION = 67 +LAST_KNOWN_API_VERSION = 69 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LOG = logging.getLogger(__name__) diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index e7bdb5117..3437aa123 100755 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -81,6 +81,9 @@ class ProvisionStateBaremetalNode(command.Command): clean_steps = getattr(parsed_args, 'clean_steps', None) clean_steps = utils.handle_json_arg(clean_steps, 'clean steps') + deploy_steps = getattr(parsed_args, 'deploy_steps', None) + deploy_steps = utils.handle_json_arg(deploy_steps, 'deploy steps') + config_drive = getattr(parsed_args, 'config_drive', None) if config_drive: try: @@ -98,6 +101,7 @@ class ProvisionStateBaremetalNode(command.Command): parsed_args.provision_state, configdrive=config_drive, cleansteps=clean_steps, + deploysteps=deploy_steps, rescue_password=rescue_password) @@ -554,6 +558,18 @@ class DeployBaremetalNode(ProvisionStateWithWait): metavar='', default=None, help=CONFIG_DRIVE_ARG_HELP) + + parser.add_argument( + '--deploy-steps', + metavar='', + required=False, + default=None, + help=_("The deploy steps in JSON format. May be the path to a " + "file containing the deploy steps; OR '-', with the deploy " + "steps being read from standard input; OR a string. The " + "value should be a list of deploy-step dictionaries; each " + "dictionary should have keys 'interface', 'step', " + "'priority' and optional key 'args'.")) return parser @@ -1029,6 +1045,18 @@ class RebuildBaremetalNode(ProvisionStateWithWait): metavar='', default=None, help=CONFIG_DRIVE_ARG_HELP) + + parser.add_argument( + '--deploy-steps', + metavar='', + required=False, + default=None, + help=_("The deploy steps in JSON format. May be the path to a " + "file containing the deploy steps; OR '-', with the deploy " + "steps being read from standard input; OR a string. The " + "value should be a list of deploy-step dictionaries; each " + "dictionary should have keys 'interface', 'step', " + "'priority' and optional key 'args'.")) return parser diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index 1c0df28a6..fd842055e 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -56,7 +56,8 @@ class TestAdopt(TestBaremetal): self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'adopt', - cleansteps=None, configdrive=None, rescue_password=None) + cleansteps=None, deploysteps=None, configdrive=None, + rescue_password=None) def test_adopt_no_wait(self): arglist = ['node_uuid'] @@ -1441,11 +1442,13 @@ class TestDeployBaremetalProvisionState(TestBaremetal): def test_deploy_baremetal_provision_state_active_and_configdrive(self): arglist = ['node_uuid', - '--config-drive', 'path/to/drive'] + '--config-drive', 'path/to/drive', + '--deploy-steps', '[{"interface":"deploy"}]'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'active'), ('config_drive', 'path/to/drive'), + ('deploy_steps', '[{"interface":"deploy"}]') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1454,7 +1457,8 @@ class TestDeployBaremetalProvisionState(TestBaremetal): self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'active', - cleansteps=None, configdrive='path/to/drive', rescue_password=None) + cleansteps=None, deploysteps=[{"interface": "deploy"}], + configdrive='path/to/drive', rescue_password=None) def test_deploy_baremetal_provision_state_active_and_configdrive_dict( self): @@ -1472,7 +1476,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal): self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'active', - cleansteps=None, configdrive={'meta_data': {}}, + cleansteps=None, deploysteps=None, configdrive={'meta_data': {}}, rescue_password=None) def test_deploy_no_wait(self): @@ -1674,8 +1678,8 @@ class TestRescueBaremetalProvisionState(TestBaremetal): self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_provision_state.assert_called_once_with( - 'node_uuid', 'rescue', cleansteps=None, configdrive=None, - rescue_password='supersecret') + 'node_uuid', 'rescue', cleansteps=None, deploysteps=None, + configdrive=None, rescue_password='supersecret') def test_rescue_baremetal_provision_state_rescue_and_wait(self): arglist = ['node_uuid', @@ -1850,11 +1854,13 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): def test_rebuild_baremetal_provision_state_active_and_configdrive(self): arglist = ['node_uuid', - '--config-drive', 'path/to/drive'] + '--config-drive', 'path/to/drive', + '--deploy-steps', '[{"interface":"deploy"}]'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'rebuild'), ('config_drive', 'path/to/drive'), + ('deploy_steps', '[{"interface":"deploy"}]') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1863,8 +1869,8 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'rebuild', - cleansteps=None, configdrive='path/to/drive', - rescue_password=None) + cleansteps=None, deploysteps=[{"interface": "deploy"}], + configdrive='path/to/drive', rescue_password=None) def test_rebuild_no_wait(self): arglist = ['node_uuid'] @@ -1879,7 +1885,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'rebuild', - cleansteps=None, configdrive=None, + cleansteps=None, deploysteps=None, configdrive=None, rescue_password=None) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() @@ -1997,8 +2003,8 @@ class TestUnrescueBaremetalProvisionState(TestBaremetal): self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_provision_state.assert_called_once_with( - 'node_uuid', 'unrescue', cleansteps=None, configdrive=None, - rescue_password=None) + 'node_uuid', 'unrescue', cleansteps=None, deploysteps=None, + configdrive=None, rescue_password=None) def test_unrescue_baremetal_provision_state_active_and_wait(self): arglist = ['node_uuid', diff --git a/ironicclient/tests/unit/v1/test_node.py b/ironicclient/tests/unit/v1/test_node.py index 47347ef46..4a6c3446e 100644 --- a/ironicclient/tests/unit/v1/test_node.py +++ b/ironicclient/tests/unit/v1/test_node.py @@ -1564,6 +1564,31 @@ class NodeManagerTest(testtools.TestCase): ] self.assertEqual(expect, self.api.calls) + def test_node_set_provision_state_with_deploysteps(self): + deploysteps = [{"step": "upgrade", "interface": "deploy"}] + target_state = 'active' + self.mgr.set_provision_state(NODE1['uuid'], target_state, + deploysteps=deploysteps) + body = {'target': target_state, 'deploy_steps': deploysteps} + expect = [ + ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body), + ] + self.assertEqual(expect, self.api.calls) + + def test_node_set_provision_state_with_configdrive_and_deploysteps(self): + deploysteps = [{"step": "upgrade", "interface": "deploy"}] + target_state = 'active' + self.mgr.set_provision_state(NODE1['uuid'], target_state, + configdrive={'user_data': ''}, + deploysteps=deploysteps) + body = {'target': target_state, + 'configdrive': {'user_data': ''}, + 'deploy_steps': deploysteps} + expect = [ + ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body), + ] + self.assertEqual(expect, self.api.calls) + def test_node_set_provision_state_with_rescue_password(self): rescue_password = 'supersecret' target_state = 'rescue' diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py index 5fb387289..88879f0f2 100644 --- a/ironicclient/v1/node.py +++ b/ironicclient/v1/node.py @@ -619,7 +619,7 @@ class NodeManager(base.CreateManager): def set_provision_state( self, node_uuid, state, configdrive=None, cleansteps=None, rescue_password=None, os_ironic_api_version=None, - global_request_id=None): + global_request_id=None, deploysteps=None): """Set the provision state for the node. :param node_uuid: The UUID or name of the node. @@ -644,8 +644,12 @@ class NodeManager(base.CreateManager): the request. If not specified, the client's default is used. :param global_request_id: String containing global request ID header value (in form "req-") to use for the request. - - :raises: InvalidAttribute if there was an error with the clean steps + :param deploysteps: The deploy steps as a list of deploy-step + dictionaries; each dictionary should have keys 'interface', 'step', + 'priority', and optional key 'args'. This is optional and is + only valid when setting provision-state to 'active' or 'rebuild'. + :raises: InvalidAttribute if there was an error with the clean steps or + deploy steps :returns: The status of the request """ @@ -671,6 +675,9 @@ class NodeManager(base.CreateManager): elif rescue_password: body['rescue_password'] = rescue_password + if deploysteps: + body['deploy_steps'] = deploysteps + return self.update(path, body, http_method='PUT', os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) diff --git a/releasenotes/notes/add-deploy-steps-arg-0b127e29c8cf976d.yaml b/releasenotes/notes/add-deploy-steps-arg-0b127e29c8cf976d.yaml new file mode 100644 index 000000000..f6a2e3d73 --- /dev/null +++ b/releasenotes/notes/add-deploy-steps-arg-0b127e29c8cf976d.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Adds support for providing optional deploy steps when deploying or + rebuilding; available with ironic-api-version 1.69 or higher. Baremetal CLI + is ``baremetal node --deploy-steps + `` where ```` is 'deploy' or 'rebuild' and + ```` are deploy steps in JSON format. May be path to a file + containing deploy steps; OR '-', with the deploy steps being read from + standard input; OR a string. The value should be a list of deploy-step + dictionaries; each dictionary should have keys 'interface', 'step' and + 'priority', and optional key 'args'. When overlapping, these steps override + deploy template and driver steps. For more information see + `Deploy Steps in Node Deployment documentation `_. +