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:
parent
7fb95d341c
commit
8df29e00a2
@ -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__)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -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'
|
||||||
|
@ -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)
|
||||||
|
@ -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>`_.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user