Add soft reboot/poweroff power states.

Soft reboot/poweroff is now being implemented in Ironic. And the
Ironic driver in Nova should support this new feature. This patch
adds soft reboot and soft poweroff states.

Co-Authored-By: Naohiro Tamura <naohirot@jp.fujitsu.com>

Partial-Bug: #1526226
Change-Id: Icd2859784f3df85c08162c14464bce58067aab1d
Depends-On: I1c9bbd1f11f6a8565607c874b3c99aa10eeb62a5
This commit is contained in:
Tang Chen 2015-11-21 11:19:08 +08:00 committed by Julia Kreger
parent 2fd05f3cf3
commit 249215e3d5
6 changed files with 155 additions and 15 deletions

View File

@ -268,9 +268,10 @@ class FunctionalTestBase(base.ClientTestBase):
'node-set-maintenance',
params='{0} {1} {2}'.format(node_id, maintenance_mode, params))
def set_node_power_state(self, node_id, power_state):
def set_node_power_state(self, node_id, power_state, params=''):
self.ironic('node-set-power-state',
params='{0} {1}'.format(node_id, power_state))
params='{0} {1} {2}'
.format(node_id, power_state, params))
def set_node_provision_state(self, node_id, provision_state, params=''):
self.ironic('node-set-provision-state',

View File

@ -59,8 +59,8 @@ PORTGROUP = {'uuid': '11111111-2222-3333-4444-555555555555',
'address': 'AA:BB:CC:DD:EE:FF',
'extra': {}}
POWER_STATE = {'power_state': 'power off',
'target_power_state': 'power on'}
POWER_STATE = {'power_state': 'power on',
'target_power_state': 'power off'}
DRIVER_IFACES = {'deploy': {'result': True},
'power': {'result': False, 'reason': 'Invalid IPMI username'},
@ -891,13 +891,56 @@ class NodeManagerTest(testtools.TestCase):
self.assertIsNone(maintenance)
def test_node_set_power_state(self):
power_state = self.mgr.set_power_state(NODE1['uuid'], "on")
body = {'target': 'power on'}
power_state = self.mgr.set_power_state(NODE1['uuid'], "off")
body = {'target': 'power off'}
expect = [
('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('power on', power_state.target_power_state)
self.assertEqual('power off', power_state.target_power_state)
def test_node_set_power_timeout(self):
power_state = self.mgr.set_power_state(NODE1['uuid'], "off", timeout=2)
body = {'target': 'power off', 'timeout': 2}
expect = [
('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('power off', power_state.target_power_state)
def test_node_set_power_timeout_str(self):
power_state = self.mgr.set_power_state(NODE1['uuid'], "off",
timeout="2")
body = {'target': 'power off', 'timeout': 2}
expect = [
('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('power off', power_state.target_power_state)
def test_node_set_power_state_soft(self):
power_state = self.mgr.set_power_state(NODE1['uuid'], "off", soft=True)
body = {'target': 'soft power off'}
expect = [
('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual('power off', power_state.target_power_state)
def test_node_set_power_state_soft_fail(self):
self.assertRaises(ValueError,
self.mgr.set_power_state,
NODE1['uuid'], 'on', soft=True)
def test_node_set_power_state_on_timeout_fail(self):
self.assertRaises(ValueError,
self.mgr.set_power_state,
NODE1['uuid'], 'off', soft=False, timeout=0)
def test_node_set_power_state_on_timeout_type_error(self):
self.assertRaises(ValueError,
self.mgr.set_power_state,
NODE1['uuid'], 'off', soft=False, timeout='a')
def test_set_target_raid_config(self):
self.mgr.set_target_raid_config(

View File

@ -376,15 +376,26 @@ class NodeShellTest(utils.BaseTestCase):
n_shell.do_node_set_maintenance,
client_mock, args)
def _do_node_set_power_state_helper(self, power_state):
def _do_node_set_power_state_helper(self, power_state,
soft=False, timeout=None, error=False):
client_mock = mock.MagicMock()
args = mock.MagicMock()
args.node = 'node_uuid'
args.power_state = power_state
args.soft = soft
args.power_timeout = timeout
n_shell.do_node_set_power_state(client_mock, args)
client_mock.node.set_power_state.assert_called_once_with('node_uuid',
power_state)
if error:
client_mock.node = mock.MagicMock()
client_mock.node.set_power_state = mock.MagicMock()
client_mock.node.set_power_state.side_effect = ValueError("fake")
self.assertRaises(exc.CommandError,
n_shell.do_node_set_power_state,
client_mock, args)
else:
n_shell.do_node_set_power_state(client_mock, args)
client_mock.node.set_power_state.assert_called_once_with(
'node_uuid', power_state, soft, timeout=timeout)
def test_do_node_set_power_state_on(self):
self._do_node_set_power_state_helper('on')
@ -395,6 +406,37 @@ class NodeShellTest(utils.BaseTestCase):
def test_do_node_set_power_state_reboot(self):
self._do_node_set_power_state_helper('reboot')
def test_do_node_set_power_state_on_timeout(self):
self._do_node_set_power_state_helper('on', timeout=10)
def test_do_node_set_power_state_on_timeout_fail(self):
self._do_node_set_power_state_helper('on', timeout=0, error=True)
def test_do_node_set_power_state_off_timeout(self):
self._do_node_set_power_state_helper('off', timeout=10)
def test_do_node_set_power_state_reboot_timeout(self):
self._do_node_set_power_state_helper('reboot', timeout=10)
def test_do_node_set_power_state_soft_on_fail(self):
self._do_node_set_power_state_helper('on', soft=True, error=True)
def test_do_node_set_power_state_soft_off(self):
self._do_node_set_power_state_helper('off', soft=True)
def test_do_node_set_power_state_soft_reboot(self):
self._do_node_set_power_state_helper('reboot', soft=True)
def test_do_node_set_power_state_soft_on_timeout_fail(self):
self._do_node_set_power_state_helper('on', soft=True, timeout=10,
error=True)
def test_do_node_set_power_state_soft_off_timeout(self):
self._do_node_set_power_state_helper('off', soft=True, timeout=10)
def test_do_node_set_power_state_soft_reboot_timeout(self):
self._do_node_set_power_state_helper('reboot', soft=True, timeout=10)
def test_do_node_set_target_raid_config_file(self):
contents = '{"raid": "config"}'

View File

@ -28,6 +28,8 @@ _power_states = {
'on': 'power on',
'off': 'power off',
'reboot': 'rebooting',
'soft off': 'soft power off',
'soft reboot': 'soft rebooting',
}
@ -273,10 +275,39 @@ class NodeManager(base.CreateManager):
else:
return self.delete(path)
def set_power_state(self, node_id, state):
def set_power_state(self, node_id, state, soft=False, timeout=None):
"""Sets power state for a node.
:param node_id: Node identifier
:param state: One of target power state, 'on', 'off', or 'reboot'
:param soft: The flag for graceful power 'off' or 'reboot'
:param timeout: The timeout (in seconds) positive integer value (> 0)
:raises: ValueError if 'soft' or 'timeout' option is invalid
:returns: The status of the request
"""
if state == 'on' and soft:
raise ValueError(
_("'soft' option is invalid for the power-state 'on'"))
path = "%s/states/power" % node_id
target = {'target': _power_states.get(state, state)}
return self.update(path, target, http_method='PUT')
requested_state = 'soft ' + state if soft else state
target = _power_states.get(requested_state, state)
body = {'target': target}
if timeout is not None:
msg = _("'timeout' option for setting power state must have "
"positive integer value (> 0)")
try:
timeout = int(timeout)
except (ValueError, TypeError):
raise ValueError(msg)
if timeout <= 0:
raise ValueError(msg)
body = {'target': target, 'timeout': timeout}
return self.update(path, body, http_method='PUT')
def set_target_raid_config(self, node_ident, target_raid_config):
"""Sets target_raid_config for a node.

View File

@ -14,6 +14,7 @@
# under the License.
import argparse
import six
from ironicclient.common.apiclient import exceptions
from ironicclient.common import cliutils
@ -409,9 +410,27 @@ def do_node_set_maintenance(cc, args):
metavar='<power-state>',
choices=['on', 'off', 'reboot'],
help="'on', 'off', or 'reboot'.")
@cliutils.arg(
'--soft',
dest='soft',
action='store_true',
default=False,
help=("Gracefully change the power state. Only valid for 'off' and "
"'reboot' power states."))
@cliutils.arg(
'--power-timeout',
metavar='<power-timeout>',
type=int,
default=None,
help=("Timeout (in seconds, positive integer) to wait for the target "
"power state before erroring out."))
def do_node_set_power_state(cc, args):
"""Power a node on or off or reboot."""
cc.node.set_power_state(args.node, args.power_state)
try:
cc.node.set_power_state(args.node, args.power_state, args.soft,
timeout=args.power_timeout)
except ValueError as e:
raise exc.CommandError(six.text_type(e))
@cliutils.arg('node', metavar='<node>', help="Name or UUID of the node.")

View File

@ -0,0 +1,4 @@
---
features:
- Add optional arguments '--soft' and '--power-timeout' to the command
"node-set-power-state".