Add 'deploy steps' for provisioning API

Story: 2008043
Task: 41409
Depends-On: https://review.opendev.org/c/openstack/ironic/+/768353
Change-Id: I6adffcf304ca090ff551280f3ec4c9d09a5537d8
This commit is contained in:
Aija Jauntēva 2020-12-21 13:29:01 -05:00
parent 7fb95d341c
commit 8df29e00a2
6 changed files with 97 additions and 16 deletions

View File

@ -37,7 +37,7 @@ from ironicclient import exc
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
# for full details. # for full details.
DEFAULT_VER = '1.9' DEFAULT_VER = '1.9'
LAST_KNOWN_API_VERSION = 67 LAST_KNOWN_API_VERSION = 69
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)

View File

@ -81,6 +81,9 @@ class ProvisionStateBaremetalNode(command.Command):
clean_steps = getattr(parsed_args, 'clean_steps', None) clean_steps = getattr(parsed_args, 'clean_steps', None)
clean_steps = utils.handle_json_arg(clean_steps, 'clean steps') 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) config_drive = getattr(parsed_args, 'config_drive', None)
if config_drive: if config_drive:
try: try:
@ -98,6 +101,7 @@ class ProvisionStateBaremetalNode(command.Command):
parsed_args.provision_state, parsed_args.provision_state,
configdrive=config_drive, configdrive=config_drive,
cleansteps=clean_steps, cleansteps=clean_steps,
deploysteps=deploy_steps,
rescue_password=rescue_password) rescue_password=rescue_password)
@ -554,6 +558,18 @@ class DeployBaremetalNode(ProvisionStateWithWait):
metavar='<config-drive>', metavar='<config-drive>',
default=None, default=None,
help=CONFIG_DRIVE_ARG_HELP) help=CONFIG_DRIVE_ARG_HELP)
parser.add_argument(
'--deploy-steps',
metavar='<deploy-steps>',
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 return parser
@ -1029,6 +1045,18 @@ class RebuildBaremetalNode(ProvisionStateWithWait):
metavar='<config-drive>', metavar='<config-drive>',
default=None, default=None,
help=CONFIG_DRIVE_ARG_HELP) help=CONFIG_DRIVE_ARG_HELP)
parser.add_argument(
'--deploy-steps',
metavar='<deploy-steps>',
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 return parser

View File

@ -56,7 +56,8 @@ class TestAdopt(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with( self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'adopt', '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): def test_adopt_no_wait(self):
arglist = ['node_uuid'] arglist = ['node_uuid']
@ -1441,11 +1442,13 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
def test_deploy_baremetal_provision_state_active_and_configdrive(self): def test_deploy_baremetal_provision_state_active_and_configdrive(self):
arglist = ['node_uuid', arglist = ['node_uuid',
'--config-drive', 'path/to/drive'] '--config-drive', 'path/to/drive',
'--deploy-steps', '[{"interface":"deploy"}]']
verifylist = [ verifylist = [
('node', 'node_uuid'), ('node', 'node_uuid'),
('provision_state', 'active'), ('provision_state', 'active'),
('config_drive', 'path/to/drive'), ('config_drive', 'path/to/drive'),
('deploy_steps', '[{"interface":"deploy"}]')
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) 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( self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'active', '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( def test_deploy_baremetal_provision_state_active_and_configdrive_dict(
self): self):
@ -1472,7 +1476,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with( self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'active', 'node_uuid', 'active',
cleansteps=None, configdrive={'meta_data': {}}, cleansteps=None, deploysteps=None, configdrive={'meta_data': {}},
rescue_password=None) rescue_password=None)
def test_deploy_no_wait(self): def test_deploy_no_wait(self):
@ -1674,8 +1678,8 @@ class TestRescueBaremetalProvisionState(TestBaremetal):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
self.baremetal_mock.node.set_provision_state.assert_called_once_with( self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rescue', cleansteps=None, configdrive=None, 'node_uuid', 'rescue', cleansteps=None, deploysteps=None,
rescue_password='supersecret') configdrive=None, rescue_password='supersecret')
def test_rescue_baremetal_provision_state_rescue_and_wait(self): def test_rescue_baremetal_provision_state_rescue_and_wait(self):
arglist = ['node_uuid', arglist = ['node_uuid',
@ -1850,11 +1854,13 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
def test_rebuild_baremetal_provision_state_active_and_configdrive(self): def test_rebuild_baremetal_provision_state_active_and_configdrive(self):
arglist = ['node_uuid', arglist = ['node_uuid',
'--config-drive', 'path/to/drive'] '--config-drive', 'path/to/drive',
'--deploy-steps', '[{"interface":"deploy"}]']
verifylist = [ verifylist = [
('node', 'node_uuid'), ('node', 'node_uuid'),
('provision_state', 'rebuild'), ('provision_state', 'rebuild'),
('config_drive', 'path/to/drive'), ('config_drive', 'path/to/drive'),
('deploy_steps', '[{"interface":"deploy"}]')
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) 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( self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rebuild', 'node_uuid', 'rebuild',
cleansteps=None, configdrive='path/to/drive', cleansteps=None, deploysteps=[{"interface": "deploy"}],
rescue_password=None) configdrive='path/to/drive', rescue_password=None)
def test_rebuild_no_wait(self): def test_rebuild_no_wait(self):
arglist = ['node_uuid'] arglist = ['node_uuid']
@ -1879,7 +1885,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with( self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rebuild', 'node_uuid', 'rebuild',
cleansteps=None, configdrive=None, cleansteps=None, deploysteps=None, configdrive=None,
rescue_password=None) rescue_password=None)
self.baremetal_mock.node.wait_for_provision_state.assert_not_called() 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.cmd.take_action(parsed_args)
self.baremetal_mock.node.set_provision_state.assert_called_once_with( self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'unrescue', cleansteps=None, configdrive=None, 'node_uuid', 'unrescue', cleansteps=None, deploysteps=None,
rescue_password=None) configdrive=None, rescue_password=None)
def test_unrescue_baremetal_provision_state_active_and_wait(self): def test_unrescue_baremetal_provision_state_active_and_wait(self):
arglist = ['node_uuid', arglist = ['node_uuid',

View File

@ -1564,6 +1564,31 @@ class NodeManagerTest(testtools.TestCase):
] ]
self.assertEqual(expect, self.api.calls) 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): def test_node_set_provision_state_with_rescue_password(self):
rescue_password = 'supersecret' rescue_password = 'supersecret'
target_state = 'rescue' target_state = 'rescue'

View File

@ -619,7 +619,7 @@ class NodeManager(base.CreateManager):
def set_provision_state( def set_provision_state(
self, node_uuid, state, configdrive=None, cleansteps=None, self, node_uuid, state, configdrive=None, cleansteps=None,
rescue_password=None, os_ironic_api_version=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. """Set the provision state for the node.
:param node_uuid: The UUID or name of 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. the request. If not specified, the client's default is used.
:param global_request_id: String containing global request ID header :param global_request_id: String containing global request ID header
value (in form "req-<UUID>") to use for the request. value (in form "req-<UUID>") to use for the request.
:param deploysteps: The deploy steps as a list of deploy-step
:raises: InvalidAttribute if there was an error with the clean steps 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 :returns: The status of the request
""" """
@ -671,6 +675,9 @@ class NodeManager(base.CreateManager):
elif rescue_password: elif rescue_password:
body['rescue_password'] = rescue_password body['rescue_password'] = rescue_password
if deploysteps:
body['deploy_steps'] = deploysteps
return self.update(path, body, http_method='PUT', return self.update(path, body, http_method='PUT',
os_ironic_api_version=os_ironic_api_version, os_ironic_api_version=os_ironic_api_version,
global_request_id=global_request_id) global_request_id=global_request_id)

View File

@ -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 <provision-state> <node> --deploy-steps
<deploy-steps>`` where ``<provision-state>`` is 'deploy' or 'rebuild' and
``<deploy-steps>`` 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 <https://docs.openstack.org/ironic/latest/admin/node-deployment.html#id3>`_.