Add support for RESCUE and UNRESCUE provision states

Adding support for rescue and unrescue to OSC.

Co-Authored-By: Michael Turek <mjturek@linux.vnet.ibm.com>
Co-Authored-By: Dao Cong Tien <tiendc@vn.fujitsu.com>

Change-Id: Id865d7c9a40e8d85242befb2a0335abe0c52dac7
Depends-On: I3953ff0b1ca000f8ae83fb7b3c663f848a149345
Partial-bug: 1526449
This commit is contained in:
Mario Villaplana 2017-09-07 07:41:53 +00:00 committed by Dao Cong Tien
parent 683b7c6e78
commit fce885bf64
8 changed files with 204 additions and 8 deletions

View File

@ -44,7 +44,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 = 37
LAST_KNOWN_API_VERSION = 38
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)

View File

@ -67,12 +67,14 @@ class ProvisionStateBaremetalNode(command.Command):
clean_steps = utils.handle_json_or_file_arg(clean_steps)
config_drive = getattr(parsed_args, 'config_drive', None)
rescue_password = getattr(parsed_args, 'rescue_password', None)
baremetal_client.node.set_provision_state(
parsed_args.node,
parsed_args.provision_state,
configdrive=config_drive,
cleansteps=clean_steps)
cleansteps=clean_steps,
rescuepassword=rescue_password)
class ProvisionStateWithWait(ProvisionStateBaremetalNode):
@ -926,6 +928,25 @@ class RebuildBaremetalNode(ProvisionStateWithWait):
return parser
class RescueBaremetalNode(ProvisionStateWithWait):
"""Set provision state of baremetal node to 'rescue'"""
log = logging.getLogger(__name__ + ".RescueBaremetalNode")
PROVISION_STATE = 'rescue'
def get_parser(self, prog_name):
parser = super(RescueBaremetalNode, self).get_parser(prog_name)
parser.add_argument(
'--rescue-password',
metavar='<rescue-password>',
required=True,
default=None,
help=("The password that will be used to login to the rescue "
"ramdisk. The value should be a string."))
return parser
class SetBaremetalNode(command.Command):
"""Set baremetal properties"""
@ -1221,6 +1242,13 @@ class UndeployBaremetalNode(ProvisionStateWithWait):
PROVISION_STATE = 'deleted'
class UnrescueBaremetalNode(ProvisionStateWithWait):
"""Set provision state of baremetal node to 'unrescue'"""
log = logging.getLogger(__name__ + ".UnrescueBaremetalNode")
PROVISION_STATE = 'unrescue'
class UnsetBaremetalNode(command.Command):
"""Unset baremetal properties"""
log = logging.getLogger(__name__ + ".UnsetBaremetalNode")

View File

@ -55,7 +55,8 @@ class TestAdopt(TestBaremetal):
self.cmd.take_action(parsed_args)
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'adopt', cleansteps=None, configdrive=None)
'node_uuid', 'adopt',
cleansteps=None, configdrive=None, rescuepassword=None)
def test_adopt_no_wait(self):
arglist = ['node_uuid']
@ -1199,7 +1200,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'active',
cleansteps=None, configdrive='path/to/drive')
cleansteps=None, configdrive='path/to/drive', rescuepassword=None)
def test_deploy_no_wait(self):
arglist = ['node_uuid']
@ -1379,6 +1380,80 @@ class TestCleanBaremetalProvisionState(TestBaremetal):
poll_interval=10, timeout=0)
class TestRescueBaremetalProvisionState(TestBaremetal):
def setUp(self):
super(TestRescueBaremetalProvisionState, self).setUp()
# Get the command object to test
self.cmd = baremetal_node.RescueBaremetalNode(self.app, None)
def test_rescue_baremetal_no_wait(self):
arglist = ['node_uuid',
'--rescue-password', 'supersecret']
verifylist = [
('node', 'node_uuid'),
('provision_state', 'rescue'),
('rescue_password', 'supersecret'),
]
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', 'rescue', cleansteps=None, configdrive=None,
rescuepassword='supersecret')
def test_rescue_baremetal_provision_state_rescue_and_wait(self):
arglist = ['node_uuid',
'--wait', '15',
'--rescue-password', 'supersecret']
verifylist = [
('node', 'node_uuid'),
('provision_state', 'rescue'),
('rescue_password', 'supersecret'),
('wait_timeout', 15)
]
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='rescue',
poll_interval=10, timeout=15)
def test_rescue_baremetal_provision_state_default_wait(self):
arglist = ['node_uuid',
'--wait',
'--rescue-password', 'supersecret']
verifylist = [
('node', 'node_uuid'),
('provision_state', 'rescue'),
('rescue_password', 'supersecret'),
('wait_timeout', 0)
]
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='rescue',
poll_interval=10, timeout=0)
def test_rescue_baremetal_no_rescue_password(self):
arglist = ['node_uuid']
verifylist = [('node', 'node_uuid'),
('provision_state', 'rescue')]
self.assertRaises(oscutils.ParserException,
self.check_parser,
self.cmd, arglist, verifylist)
class TestInspectBaremetalProvisionState(TestBaremetal):
def setUp(self):
super(TestInspectBaremetalProvisionState, self).setUp()
@ -1515,7 +1590,8 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rebuild',
cleansteps=None, configdrive='path/to/drive')
cleansteps=None, configdrive='path/to/drive',
rescuepassword=None)
def test_rebuild_no_wait(self):
arglist = ['node_uuid']
@ -1530,7 +1606,8 @@ class TestRebuildBaremetalProvisionState(TestBaremetal):
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
'node_uuid', 'rebuild',
cleansteps=None, configdrive=None)
cleansteps=None, configdrive=None,
rescuepassword=None)
self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
@ -1628,6 +1705,65 @@ class TestUndeployBaremetalProvisionState(TestBaremetal):
poll_interval=10, timeout=0)
class TestUnrescueBaremetalProvisionState(TestBaremetal):
def setUp(self):
super(TestUnrescueBaremetalProvisionState, self).setUp()
# Get the command object to test
self.cmd = baremetal_node.UnrescueBaremetalNode(self.app, None)
def test_unrescue_no_wait(self):
arglist = ['node_uuid']
verifylist = [
('node', 'node_uuid'),
('provision_state', 'unrescue'),
]
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', 'unrescue', cleansteps=None, configdrive=None,
rescuepassword=None)
def test_unrescue_baremetal_provision_state_active_and_wait(self):
arglist = ['node_uuid',
'--wait', '15']
verifylist = [
('node', 'node_uuid'),
('provision_state', 'unrescue'),
('wait_timeout', 15)
]
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_unrescue_baremetal_provision_state_default_wait(self):
arglist = ['node_uuid',
'--wait']
verifylist = [
('node', 'node_uuid'),
('provision_state', 'unrescue'),
('wait_timeout', 0)
]
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 TestBaremetalReboot(TestBaremetal):
def setUp(self):
super(TestBaremetalReboot, self).setUp()

View File

@ -1352,6 +1352,17 @@ class NodeManagerTest(testtools.TestCase):
]
self.assertEqual(expect, self.api.calls)
def test_node_set_provision_state_with_rescuepassword(self):
rescuepassword = 'supersecret'
target_state = 'rescue'
self.mgr.set_provision_state(NODE1['uuid'], target_state,
rescuepassword=rescuepassword)
body = {'target': target_state, 'rescue_password': rescuepassword}
expect = [
('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body),
]
self.assertEqual(expect, self.api.calls)
def test_node_states(self):
states = self.mgr.states(NODE1['uuid'])
expect = [

View File

@ -478,12 +478,13 @@ class NodeManager(base.CreateManager):
return self.get(path)
def set_provision_state(self, node_uuid, state, configdrive=None,
cleansteps=None):
cleansteps=None, rescuepassword=None):
"""Set the provision state for the node.
:param node_uuid: The UUID or name of the node.
:param state: The desired provision state. One of 'active', 'deleted',
'rebuild', 'inspect', 'provide', 'manage', 'clean', 'abort'.
'rebuild', 'inspect', 'provide', 'manage', 'clean', 'abort',
'rescue', 'unrescue'.
:param configdrive: A gzipped, base64-encoded configuration drive
string OR the path to the configuration drive file OR the path to
a directory containing the config drive files. In case it's a
@ -493,6 +494,10 @@ class NodeManager(base.CreateManager):
dictionaries; each dictionary should have keys 'interface' and
'step', and optional key 'args'. This must be specified (and is
only valid) when setting provision-state to 'clean'.
:param rescuepassword: A string to be used as the login password
inside the rescue ramdisk once a node is rescued. This must be
specified (and is only valid) when setting provision-state to
'rescue'.
:raises: InvalidAttribute if there was an error with the clean steps
:returns: The status of the request
"""
@ -509,6 +514,8 @@ class NodeManager(base.CreateManager):
body['configdrive'] = configdrive
elif cleansteps:
body['clean_steps'] = cleansteps
elif rescuepassword:
body['rescue_password'] = rescuepassword
return self.update(path, body, http_method='PUT')

View File

@ -43,6 +43,10 @@ PROVISION_ACTIONS = {
'adopt': {'expected_state': 'active',
'poll_interval': _SHORT_ACTION_POLL_INTERVAL},
'abort': None, # no support for --wait in abort
'rescue': {'expected_state': 'rescue',
'poll_interval': _LONG_ACTION_POLL_INTERVAL},
'unrescue': {'expected_state': 'active',
'poll_interval': _LONG_ACTION_POLL_INTERVAL},
}
PROVISION_STATES = list(PROVISION_ACTIONS)

View File

@ -0,0 +1,8 @@
---
features:
- |
Adds the below commands to OSC to support rescue mode for ironic
available starting with API version 1.38:
* ``openstack baremetal node rescue``
* ``openstack baremetal node unrescue``

View File

@ -66,10 +66,12 @@ openstack.baremetal.v1 =
baremetal_node_reboot = ironicclient.osc.v1.baremetal_node:RebootBaremetalNode
baremetal_node_rebuild = ironicclient.osc.v1.baremetal_node:RebuildBaremetalNode
baremetal_node_remove_trait = ironicclient.osc.v1.baremetal_node:RemoveTraitBaremetalNode
baremetal_node_rescue = ironicclient.osc.v1.baremetal_node:RescueBaremetalNode
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
baremetal_node_undeploy = ironicclient.osc.v1.baremetal_node:UndeployBaremetalNode
baremetal_node_unrescue = ironicclient.osc.v1.baremetal_node:UnrescueBaremetalNode
baremetal_node_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode
baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode
baremetal_node_vif_attach = ironicclient.osc.v1.baremetal_node:VifAttachBaremetalNode