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 # 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 = 37 LAST_KNOWN_API_VERSION = 38
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

@ -67,12 +67,14 @@ class ProvisionStateBaremetalNode(command.Command):
clean_steps = utils.handle_json_or_file_arg(clean_steps) clean_steps = utils.handle_json_or_file_arg(clean_steps)
config_drive = getattr(parsed_args, 'config_drive', None) config_drive = getattr(parsed_args, 'config_drive', None)
rescue_password = getattr(parsed_args, 'rescue_password', None)
baremetal_client.node.set_provision_state( baremetal_client.node.set_provision_state(
parsed_args.node, parsed_args.node,
parsed_args.provision_state, parsed_args.provision_state,
configdrive=config_drive, configdrive=config_drive,
cleansteps=clean_steps) cleansteps=clean_steps,
rescuepassword=rescue_password)
class ProvisionStateWithWait(ProvisionStateBaremetalNode): class ProvisionStateWithWait(ProvisionStateBaremetalNode):
@ -926,6 +928,25 @@ class RebuildBaremetalNode(ProvisionStateWithWait):
return parser 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): class SetBaremetalNode(command.Command):
"""Set baremetal properties""" """Set baremetal properties"""
@ -1221,6 +1242,13 @@ class UndeployBaremetalNode(ProvisionStateWithWait):
PROVISION_STATE = 'deleted' 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): class UnsetBaremetalNode(command.Command):
"""Unset baremetal properties""" """Unset baremetal properties"""
log = logging.getLogger(__name__ + ".UnsetBaremetalNode") log = logging.getLogger(__name__ + ".UnsetBaremetalNode")

View File

@ -55,7 +55,8 @@ class TestAdopt(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', 'adopt', cleansteps=None, configdrive=None) 'node_uuid', 'adopt',
cleansteps=None, configdrive=None, rescuepassword=None)
def test_adopt_no_wait(self): def test_adopt_no_wait(self):
arglist = ['node_uuid'] arglist = ['node_uuid']
@ -1199,7 +1200,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='path/to/drive') cleansteps=None, configdrive='path/to/drive', rescuepassword=None)
def test_deploy_no_wait(self): def test_deploy_no_wait(self):
arglist = ['node_uuid'] arglist = ['node_uuid']
@ -1379,6 +1380,80 @@ class TestCleanBaremetalProvisionState(TestBaremetal):
poll_interval=10, timeout=0) 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): class TestInspectBaremetalProvisionState(TestBaremetal):
def setUp(self): def setUp(self):
super(TestInspectBaremetalProvisionState, self).setUp() super(TestInspectBaremetalProvisionState, self).setUp()
@ -1515,7 +1590,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, configdrive='path/to/drive',
rescuepassword=None)
def test_rebuild_no_wait(self): def test_rebuild_no_wait(self):
arglist = ['node_uuid'] arglist = ['node_uuid']
@ -1530,7 +1606,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=None) cleansteps=None, configdrive=None,
rescuepassword=None)
self.baremetal_mock.node.wait_for_provision_state.assert_not_called() self.baremetal_mock.node.wait_for_provision_state.assert_not_called()
@ -1628,6 +1705,65 @@ class TestUndeployBaremetalProvisionState(TestBaremetal):
poll_interval=10, timeout=0) 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): class TestBaremetalReboot(TestBaremetal):
def setUp(self): def setUp(self):
super(TestBaremetalReboot, self).setUp() super(TestBaremetalReboot, self).setUp()

View File

@ -1352,6 +1352,17 @@ class NodeManagerTest(testtools.TestCase):
] ]
self.assertEqual(expect, self.api.calls) 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): def test_node_states(self):
states = self.mgr.states(NODE1['uuid']) states = self.mgr.states(NODE1['uuid'])
expect = [ expect = [

View File

@ -478,12 +478,13 @@ class NodeManager(base.CreateManager):
return self.get(path) return self.get(path)
def set_provision_state(self, node_uuid, state, configdrive=None, def set_provision_state(self, node_uuid, state, configdrive=None,
cleansteps=None): cleansteps=None, rescuepassword=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.
:param state: The desired provision state. One of 'active', 'deleted', :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 :param configdrive: A gzipped, base64-encoded configuration drive
string OR the path to the configuration drive file OR the path to 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 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 dictionaries; each dictionary should have keys 'interface' and
'step', and optional key 'args'. This must be specified (and is 'step', and optional key 'args'. This must be specified (and is
only valid) when setting provision-state to 'clean'. 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 :raises: InvalidAttribute if there was an error with the clean steps
:returns: The status of the request :returns: The status of the request
""" """
@ -509,6 +514,8 @@ class NodeManager(base.CreateManager):
body['configdrive'] = configdrive body['configdrive'] = configdrive
elif cleansteps: elif cleansteps:
body['clean_steps'] = cleansteps body['clean_steps'] = cleansteps
elif rescuepassword:
body['rescue_password'] = rescuepassword
return self.update(path, body, http_method='PUT') return self.update(path, body, http_method='PUT')

View File

@ -43,6 +43,10 @@ PROVISION_ACTIONS = {
'adopt': {'expected_state': 'active', 'adopt': {'expected_state': 'active',
'poll_interval': _SHORT_ACTION_POLL_INTERVAL}, 'poll_interval': _SHORT_ACTION_POLL_INTERVAL},
'abort': None, # no support for --wait in abort '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) 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_reboot = ironicclient.osc.v1.baremetal_node:RebootBaremetalNode
baremetal_node_rebuild = ironicclient.osc.v1.baremetal_node:RebuildBaremetalNode baremetal_node_rebuild = ironicclient.osc.v1.baremetal_node:RebuildBaremetalNode
baremetal_node_remove_trait = ironicclient.osc.v1.baremetal_node:RemoveTraitBaremetalNode 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_set = ironicclient.osc.v1.baremetal_node:SetBaremetalNode
baremetal_node_show = ironicclient.osc.v1.baremetal_node:ShowBaremetalNode baremetal_node_show = ironicclient.osc.v1.baremetal_node:ShowBaremetalNode
baremetal_node_trait_list = ironicclient.osc.v1.baremetal_node:ListTraitsBaremetalNode baremetal_node_trait_list = ironicclient.osc.v1.baremetal_node:ListTraitsBaremetalNode
baremetal_node_undeploy = ironicclient.osc.v1.baremetal_node:UndeployBaremetalNode 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_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode
baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode
baremetal_node_vif_attach = ironicclient.osc.v1.baremetal_node:VifAttachBaremetalNode baremetal_node_vif_attach = ironicclient.osc.v1.baremetal_node:VifAttachBaremetalNode