Add Service Steps to client

Change-Id: I46519120bcad8bdb369c455d73fafee914c20b00
This commit is contained in:
Julia Kreger 2023-08-10 16:39:55 -07:00
parent 2e8d526f0d
commit 8b9be99432
8 changed files with 179 additions and 21 deletions

View File

@ -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 = 86
LAST_KNOWN_API_VERSION = 87
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)

View File

@ -86,6 +86,9 @@ class ProvisionStateBaremetalNode(command.Command):
deploy_steps = getattr(parsed_args, 'deploy_steps', None)
deploy_steps = utils.handle_json_arg(deploy_steps, 'deploy steps')
service_steps = getattr(parsed_args, 'service_steps', None)
service_steps = utils.handle_json_arg(service_steps, 'service steps')
config_drive = getattr(parsed_args, 'config_drive', None)
if config_drive:
try:
@ -105,7 +108,8 @@ class ProvisionStateBaremetalNode(command.Command):
configdrive=config_drive,
cleansteps=clean_steps,
deploysteps=deploy_steps,
rescue_password=rescue_password)
rescue_password=rescue_password,
servicesteps=service_steps)
class ProvisionStateWithWait(ProvisionStateBaremetalNode):
@ -300,6 +304,29 @@ class CleanBaremetalNode(ProvisionStateWithWait):
return parser
class ServiceBaremetalNode(ProvisionStateWithWait):
"""Set provision state of baremetal node to 'service'"""
log = logging.getLogger(__name__ + ".ServiceBaremetalNode")
PROVISION_STATE = 'service'
def get_parser(self, prog_name):
parser = super(ServiceBaremetalNode, self).get_parser(prog_name)
parser.add_argument(
'--service-steps',
metavar='<service-steps>',
required=True,
default=None,
help=_("The service steps. May be the path to a YAML file "
"containing the service steps; OR '-', with the service "
" steps being read from standard input; OR a JSON string. "
"The value should be a list of service-step dictionaries; "
"each dictionary should have keys 'interface' and 'step', "
"and optional key 'args'."))
return parser
class ConsoleDisableBaremetalNode(command.Command):
"""Disable console access for a node"""

View File

@ -59,7 +59,7 @@ class TestAbort(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'abort', cleansteps=None, configdrive=None,
deploysteps=None, rescue_password=None)
deploysteps=None, rescue_password=None, servicesteps=None)
class TestAdopt(TestBaremetal):
@ -83,7 +83,7 @@ class TestAdopt(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'adopt',
cleansteps=None, deploysteps=None, configdrive=None,
rescue_password=None)
rescue_password=None, servicesteps=None)
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
def test_adopt_baremetal_provision_state_active_and_wait(self):
@ -103,7 +103,7 @@ class TestAdopt(TestBaremetal):
test_node.set_provision_state.assert_called_once_with(
'node_uuid', 'adopt',
cleansteps=None, deploysteps=None, configdrive=None,
rescue_password=None)
rescue_password=None, servicesteps=None)
test_node.wait_for_provision_state.assert_called_once_with(
['node_uuid'], expected_state='active',
poll_interval=2, timeout=15)
@ -125,7 +125,7 @@ class TestAdopt(TestBaremetal):
test_node.set_provision_state.assert_called_once_with(
'node_uuid', 'adopt',
cleansteps=None, deploysteps=None, configdrive=None,
rescue_password=None)
rescue_password=None, servicesteps=None)
test_node.wait_for_provision_state.assert_called_once_with(
['node_uuid'], expected_state='active',
poll_interval=2, timeout=0)
@ -174,7 +174,43 @@ class TestClean(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'clean', cleansteps=steps_dict, configdrive=None,
deploysteps=None, rescue_password=None)
deploysteps=None, rescue_password=None, servicesteps=None)
class TestService(TestBaremetal):
def setUp(self):
super(TestService, self).setUp()
# Get the command object to test
self.cmd = baremetal_node.ServiceBaremetalNode(self.app, None)
def test_service_with_steps(self):
steps_dict = {
"service_steps": [{
"interface": "raid",
"step": "create_configuration",
"args": {"create_nonroot_volumes": False}
}, {
"interface": "deploy",
"step": "erase_devices"
}]
}
steps_json = json.dumps(steps_dict)
arglist = ['--service-steps', steps_json, 'node_uuid']
verifylist = [
('service_steps', steps_json),
('provision_state', 'service'),
('nodes', ['node_uuid']),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'service', cleansteps=None, configdrive=None,
deploysteps=None, rescue_password=None, servicesteps=steps_dict)
class TestInspect(TestBaremetal):
@ -197,7 +233,7 @@ class TestInspect(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'inspect', cleansteps=None, configdrive=None,
deploysteps=None, rescue_password=None)
deploysteps=None, rescue_password=None, servicesteps=None)
class TestManage(TestBaremetal):
@ -220,7 +256,7 @@ class TestManage(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'manage', cleansteps=None, configdrive=None,
deploysteps=None, rescue_password=None)
deploysteps=None, rescue_password=None, servicesteps=None)
class TestProvide(TestBaremetal):
@ -243,7 +279,7 @@ class TestProvide(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'provide', cleansteps=None, configdrive=None,
deploysteps=None, rescue_password=None)
deploysteps=None, rescue_password=None, servicesteps=None)
class TestRebuild(TestBaremetal):
@ -266,7 +302,7 @@ class TestRebuild(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rebuild', cleansteps=None, configdrive=None,
deploysteps=None, rescue_password=None)
deploysteps=None, rescue_password=None, servicesteps=None)
class TestUndeploy(TestBaremetal):
@ -289,7 +325,7 @@ class TestUndeploy(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'deleted', cleansteps=None, configdrive=None,
deploysteps=None, rescue_password=None)
deploysteps=None, rescue_password=None, servicesteps=None)
class TestBootdeviceSet(TestBaremetal):
@ -1875,7 +1911,8 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'active',
cleansteps=None, deploysteps=[{"interface": "deploy"}],
configdrive='path/to/drive', rescue_password=None)
configdrive='path/to/drive', rescue_password=None,
servicesteps=None)
def test_deploy_baremetal_provision_state_active_and_configdrive_dict(
self):
@ -1894,7 +1931,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'active',
cleansteps=None, deploysteps=None, configdrive={'meta_data': {}},
rescue_password=None)
rescue_password=None, servicesteps=None)
def test_deploy_no_wait(self):
arglist = ['node_uuid']
@ -1958,7 +1995,8 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
test_node = self.baremetal_mock.node
test_node.set_provision_state.assert_has_calls([
mock.call(n, 'active', cleansteps=None, deploysteps=None,
configdrive=None, rescue_password=None)
configdrive=None, rescue_password=None,
servicesteps=None)
for n in ['node_uuid', 'node_name']
])
test_node.wait_for_provision_state.assert_called_once_with(
@ -2097,6 +2135,68 @@ class TestCleanBaremetalProvisionState(TestBaremetal):
poll_interval=10, timeout=0)
class TestServiceBaremetalProvisionState(TestBaremetal):
def setUp(self):
super(TestServiceBaremetalProvisionState, self).setUp()
# Get the command object to test
self.cmd = baremetal_node.ServiceBaremetalNode(self.app, None)
def test_service_no_wait(self):
arglist = ['node_uuid', '--service-steps', '-']
verifylist = [
('nodes', ['node_uuid']),
('provision_state', 'service'),
('service_steps', '-')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
def test_service_baremetal_provision_state_manageable_and_wait(self):
arglist = ['node_uuid',
'--wait', '15',
'--service-steps', '-']
verifylist = [
('nodes', ['node_uuid']),
('provision_state', 'service'),
('wait_timeout', 15),
('service_steps', '-')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
test_node = self.baremetal_mock.node
test_node.wait_for_provision_state.assert_called_once_with(
['node_uuid'], expected_state='active',
poll_interval=10, timeout=15)
def test_service_baremetal_provision_state_default_wait(self):
arglist = ['node_uuid',
'--wait',
'--service-steps', '-']
verifylist = [
('nodes', ['node_uuid']),
('provision_state', 'service'),
('wait_timeout', 0),
('service_steps', '-')
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
test_node = self.baremetal_mock.node
test_node.wait_for_provision_state.assert_called_once_with(
['node_uuid'], expected_state='active',
poll_interval=10, timeout=0)
class TestRescueBaremetalProvisionState(TestBaremetal):
def setUp(self):
super(TestRescueBaremetalProvisionState, self).setUp()
@ -2119,7 +2219,8 @@ class TestRescueBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rescue', cleansteps=None, deploysteps=None,
configdrive=None, rescue_password='supersecret')
configdrive=None, rescue_password='supersecret',
servicesteps=None)
def test_rescue_baremetal_provision_state_rescue_and_wait(self):
arglist = ['node_uuid',
@ -2310,7 +2411,8 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rebuild',
cleansteps=None, deploysteps=[{"interface": "deploy"}],
configdrive='path/to/drive', rescue_password=None)
configdrive='path/to/drive', rescue_password=None,
servicesteps=None)
def test_rebuild_no_wait(self):
arglist = ['node_uuid']
@ -2326,7 +2428,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rebuild',
cleansteps=None, deploysteps=None, configdrive=None,
rescue_password=None)
rescue_password=None, servicesteps=None)
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
@ -2444,7 +2546,7 @@ class TestUnrescueBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'unrescue', cleansteps=None, deploysteps=None,
configdrive=None, rescue_password=None)
configdrive=None, rescue_password=None, servicesteps=None)
def test_unrescue_baremetal_provision_state_active_and_wait(self):
arglist = ['node_uuid',
@ -4559,7 +4661,7 @@ class TestUnholdBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'unhold', cleansteps=None, deploysteps=None,
configdrive=None, rescue_password=None)
configdrive=None, rescue_password=None, servicesteps=None)
class TestListFirmwareComponents(TestBaremetal):

View File

@ -1778,6 +1778,17 @@ class NodeManagerTest(testtools.TestCase):
]
self.assertEqual(expect, self.api.calls)
def test_node_set_provision_state_with_servicesteps(self):
servicesteps = [{"step": "magic", "interface": "deploy"}]
target_state = 'service'
self.mgr.set_provision_state(NODE1['uuid'], target_state,
servicesteps=servicesteps)
body = {'target': target_state, 'service_steps': servicesteps}
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'

View File

@ -723,7 +723,8 @@ 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, deploysteps=None):
global_request_id=None, deploysteps=None,
servicesteps=None):
"""Set the provision state for the node.
:param node_uuid: The UUID or name of the node.
@ -757,6 +758,10 @@ class NodeManager(base.CreateManager):
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'.
:param servicesteps: The service steps as list of service-step
dictionaries; each dictonary should have keys 'interface', 'step',
and optional key 'args' when setting an 'active' nodes to
'service'.
:raises: InvalidAttribute if there was an error with the clean steps or
deploy steps
:returns: The status of the request
@ -794,6 +799,9 @@ class NodeManager(base.CreateManager):
if deploysteps:
body['deploy_steps'] = deploysteps
if servicesteps:
body['service_steps'] = servicesteps
return self.update(path, body, http_method='PUT',
os_ironic_api_version=os_ironic_api_version,
global_request_id=global_request_id)

View File

@ -47,6 +47,8 @@ PROVISION_ACTIONS = {
'poll_interval': _LONG_ACTION_POLL_INTERVAL},
'unrescue': {'expected_state': 'active',
'poll_interval': _LONG_ACTION_POLL_INTERVAL},
'service': {'expected_state': 'active',
'poll_interval': _LONG_ACTION_POLL_INTERVAL},
}
PROVISION_STATES = list(PROVISION_ACTIONS)

View File

@ -0,0 +1,7 @@
---
features:
- |
Increments the supported API version to 1.87.
- |
Adds the ability to request Ironic execute service steps upon an
``active`` node, utilizing the ``baremetal node service`` command.

View File

@ -93,6 +93,7 @@ openstack.baremetal.v1 =
baremetal_node_rescue = ironicclient.osc.v1.baremetal_node:RescueBaremetalNode
baremetal_node_secure_boot_on = ironicclient.osc.v1.baremetal_node:SecurebootOnBaremetalNode
baremetal_node_secure_boot_off = ironicclient.osc.v1.baremetal_node:SecurebootOffBaremetalNode
baremetal_node_service = ironicclient.osc.v1.baremetal_node:ServiceBaremetalNode
baremetal_node_set = ironicclient.osc.v1.baremetal_node:SetBaremetalNode
baremetal_node_show = ironicclient.osc.v1.baremetal_node:ShowBaremetalNode
baremetal_node_trait_list = ironicclient.osc.v1.baremetal_node:ListTraitsBaremetalNode