Consistent and more valid strings for Booleans
The set of valid input strings for Boolean values was inconsistent among the subcommands of the Ironic CLI. egs: -for node-set-console-mode: 'true', 'false' -for node-set-maintenance: 'on', 'off', 'true', 'false', 'True', 'False' This change allows the same set of valid values for all the subcommands: '0', '1', 'f', 'false', 'n', 'no', 'off', 'on', 't', 'true', 'y', 'yes'. For invalid strings, we output the subcommand usage along with the argument, invalid string, and valid string choices. At the API level, if NodeManager.set_maintenance() is passed an invalid state (maintenance mode) value, an InvalidAttribute exception is raised. Change-Id: I541695388489cdc640265df8ee6a9999e1442870 Closes-Bug: #1415943
This commit is contained in:
parent
a68770bbb3
commit
8fd2ac3ba1
ironicclient
@ -26,6 +26,7 @@ import subprocess
|
||||
import tempfile
|
||||
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import strutils
|
||||
|
||||
from ironicclient.common.i18n import _
|
||||
from ironicclient import exc
|
||||
@ -249,3 +250,30 @@ def check_empty_arg(arg, arg_descriptor):
|
||||
if not arg.strip():
|
||||
raise exc.CommandError(_('%(arg)s cannot be empty or only have blank'
|
||||
' spaces') % {'arg': arg_descriptor})
|
||||
|
||||
|
||||
def bool_argument_value(arg_name, bool_str, strict=True, default=False):
|
||||
"""Returns the Boolean represented by bool_str.
|
||||
|
||||
Returns the Boolean value for the argument named arg_name. The value is
|
||||
represented by the string bool_str. If the string is an invalid Boolean
|
||||
string: if strict is True, a CommandError exception is raised; otherwise
|
||||
the default value is returned.
|
||||
|
||||
:param arg_name: The name of the argument
|
||||
:param bool_str: The string representing a Boolean value
|
||||
:param strict: Used if the string is invalid. If True, raises an exception.
|
||||
If False, returns the default value.
|
||||
:param default: The default value to return if the string is invalid
|
||||
and not strict
|
||||
:returns: the Boolean value represented by bool_str or the default value
|
||||
if bool_str is invalid and strict is False
|
||||
:raises CommandError: if bool_str is an invalid Boolean string
|
||||
|
||||
"""
|
||||
try:
|
||||
val = strutils.bool_from_string(bool_str, strict, default)
|
||||
except ValueError as e:
|
||||
raise exc.CommandError(_("argument %(arg)s: %(err)s.")
|
||||
% {'arg': arg_name, 'err': e})
|
||||
return val
|
||||
|
@ -253,7 +253,8 @@ class IronicShell(object):
|
||||
parser = self.get_base_parser()
|
||||
|
||||
self.subcommands = {}
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>')
|
||||
subparsers = parser.add_subparsers(metavar='<subcommand>',
|
||||
dest='subparser_name')
|
||||
submodule = utils.import_versioned_module(version, 'shell')
|
||||
submodule.enhance_parser(parser, subparsers, self.subcommands)
|
||||
utils.define_commands_from_module(subparsers, self, self.subcommands)
|
||||
@ -536,6 +537,9 @@ class IronicShell(object):
|
||||
args.func(client, args)
|
||||
except exc.Unauthorized:
|
||||
raise exc.CommandError(_("Invalid OpenStack Identity credentials"))
|
||||
except exc.CommandError as e:
|
||||
subcommand_parser = self.subcommands[args.subparser_name]
|
||||
subcommand_parser.error(e)
|
||||
|
||||
@cliutils.arg('command', metavar='<subcommand>', nargs='?',
|
||||
help='Display help for <subcommand>')
|
||||
|
@ -95,6 +95,25 @@ class UtilsTest(test_utils.BaseTestCase):
|
||||
self.assertRaises(exc.CommandError,
|
||||
utils.split_and_deserialize, 'foo:bar')
|
||||
|
||||
def test_bool_arg_value(self):
|
||||
self.assertTrue(utils.bool_argument_value('arg', 'y', strict=True))
|
||||
self.assertTrue(utils.bool_argument_value('arg', 'TrUe', strict=True))
|
||||
self.assertTrue(utils.bool_argument_value('arg', '1', strict=True))
|
||||
self.assertTrue(utils.bool_argument_value('arg', 1, strict=True))
|
||||
|
||||
self.assertFalse(utils.bool_argument_value('arg', '0', strict=True))
|
||||
self.assertFalse(utils.bool_argument_value('arg', 'No', strict=True))
|
||||
|
||||
self.assertRaises(exc.CommandError,
|
||||
utils.bool_argument_value, 'arg', 'ee', strict=True)
|
||||
|
||||
self.assertFalse(utils.bool_argument_value('arg', 'ee', strict=False))
|
||||
self.assertTrue(utils.bool_argument_value('arg', 'ee', strict=False,
|
||||
default=True))
|
||||
# No check that default is a Boolean...
|
||||
self.assertEqual('foo', utils.bool_argument_value('arg', 'ee',
|
||||
strict=False, default='foo'))
|
||||
|
||||
|
||||
class CommonParamsForListTest(test_utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
|
@ -66,6 +66,8 @@ CONSOLE_DATA_ENABLED = {'console_enabled': True,
|
||||
'console_info': {'test-console': 'test-console-data'}}
|
||||
CONSOLE_DATA_DISABLED = {'console_enabled': False, 'console_info': None}
|
||||
|
||||
CONSOLE_ENABLE = 'true'
|
||||
|
||||
BOOT_DEVICE = {'boot_device': 'pxe', 'persistent': False}
|
||||
SUPPORTED_BOOT_DEVICE = {'supported_boot_devices': ['pxe']}
|
||||
|
||||
@ -232,7 +234,7 @@ fake_responses = {
|
||||
CONSOLE_DATA_ENABLED,
|
||||
),
|
||||
'PUT': (
|
||||
{'enabled': 'true'},
|
||||
{'enabled': CONSOLE_ENABLE},
|
||||
None,
|
||||
),
|
||||
},
|
||||
@ -422,6 +424,15 @@ class NodeManagerTest(testtools.TestCase):
|
||||
self.assertThat(nodes, HasLength(1))
|
||||
self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid'))
|
||||
|
||||
def test_node_list_unassociated_string(self):
|
||||
nodes = self.mgr.list(associated="False")
|
||||
expect = [
|
||||
('GET', '/v1/nodes/?associated=False', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(nodes, HasLength(1))
|
||||
self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid'))
|
||||
|
||||
def test_node_list_maintenance(self):
|
||||
nodes = self.mgr.list(maintenance=True)
|
||||
expect = [
|
||||
@ -431,6 +442,15 @@ class NodeManagerTest(testtools.TestCase):
|
||||
self.assertThat(nodes, HasLength(1))
|
||||
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
|
||||
|
||||
def test_node_list_maintenance_string(self):
|
||||
nodes = self.mgr.list(maintenance="True")
|
||||
expect = [
|
||||
('GET', '/v1/nodes/?maintenance=True', {}, None),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertThat(nodes, HasLength(1))
|
||||
self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid'))
|
||||
|
||||
def test_node_list_no_maintenance(self):
|
||||
nodes = self.mgr.list(maintenance=False)
|
||||
expect = [
|
||||
@ -621,6 +641,20 @@ class NodeManagerTest(testtools.TestCase):
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(None, maintenance)
|
||||
|
||||
def test_node_set_maintenance_bad(self):
|
||||
self.assertRaises(exc.InvalidAttribute, self.mgr.set_maintenance,
|
||||
NODE1['uuid'], 'bad')
|
||||
|
||||
def test_node_set_maintenance_bool(self):
|
||||
maintenance = self.mgr.set_maintenance(NODE1['uuid'], True,
|
||||
maint_reason='reason')
|
||||
body = {'reason': 'reason'}
|
||||
expect = [
|
||||
('PUT', '/v1/nodes/%s/maintenance' % NODE1['uuid'], {}, body),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
self.assertEqual(None, maintenance)
|
||||
|
||||
def test_node_set_power_state(self):
|
||||
power_state = self.mgr.set_power_state(NODE1['uuid'], "on")
|
||||
body = {'target': 'power on'}
|
||||
@ -705,13 +739,17 @@ class NodeManagerTest(testtools.TestCase):
|
||||
sorted(states.to_dict().keys()))
|
||||
|
||||
def test_node_set_console_mode(self):
|
||||
enabled = 'true'
|
||||
self.mgr.set_console_mode(NODE1['uuid'], enabled)
|
||||
body = {'enabled': enabled}
|
||||
expect = [
|
||||
('PUT', '/v1/nodes/%s/states/console' % NODE1['uuid'], {}, body),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
global ENABLE
|
||||
for enabled in ['true', True, 'False', False]:
|
||||
self.api.calls = []
|
||||
ENABLE = enabled
|
||||
self.mgr.set_console_mode(NODE1['uuid'], enabled)
|
||||
body = {'enabled': enabled}
|
||||
expect = [
|
||||
('PUT', '/v1/nodes/%s/states/console' % NODE1['uuid'], {},
|
||||
body),
|
||||
]
|
||||
self.assertEqual(expect, self.api.calls)
|
||||
|
||||
def test_node_get_console(self):
|
||||
info = self.mgr.get_console(NODE1['uuid'])
|
||||
|
@ -222,7 +222,7 @@ class NodeShellTest(utils.BaseTestCase):
|
||||
|
||||
n_shell.do_node_set_maintenance(client_mock, args)
|
||||
client_mock.node.set_maintenance.assert_called_once_with(
|
||||
'node_uuid', 'true', maint_reason='reason')
|
||||
'node_uuid', True, maint_reason='reason')
|
||||
|
||||
def test_do_node_set_maintenance_false(self):
|
||||
client_mock = mock.MagicMock()
|
||||
@ -234,7 +234,19 @@ class NodeShellTest(utils.BaseTestCase):
|
||||
|
||||
n_shell.do_node_set_maintenance(client_mock, args)
|
||||
client_mock.node.set_maintenance.assert_called_once_with(
|
||||
'node_uuid', 'false', maint_reason=None)
|
||||
'node_uuid', False, maint_reason=None)
|
||||
|
||||
def test_do_node_set_maintenance_bad(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.node = 'node_uuid'
|
||||
args.maintenance_mode = 'yuck'
|
||||
# NOTE(jroll) None is the default. <3 mock.
|
||||
args.reason = None
|
||||
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
n_shell.do_node_set_maintenance, client_mock, args)
|
||||
self.assertFalse(client_mock.node.set_maintenance.called)
|
||||
|
||||
def test_do_node_set_maintenance_false_with_reason_fails(self):
|
||||
client_mock = mock.MagicMock()
|
||||
@ -256,7 +268,7 @@ class NodeShellTest(utils.BaseTestCase):
|
||||
|
||||
n_shell.do_node_set_maintenance(client_mock, args)
|
||||
client_mock.node.set_maintenance.assert_called_once_with(
|
||||
'node_uuid', 'on', maint_reason='reason')
|
||||
'node_uuid', True, maint_reason='reason')
|
||||
|
||||
def test_do_node_set_maintenance_off(self):
|
||||
client_mock = mock.MagicMock()
|
||||
@ -268,7 +280,7 @@ class NodeShellTest(utils.BaseTestCase):
|
||||
|
||||
n_shell.do_node_set_maintenance(client_mock, args)
|
||||
client_mock.node.set_maintenance.assert_called_once_with(
|
||||
'node_uuid', 'off', maint_reason=None)
|
||||
'node_uuid', False, maint_reason=None)
|
||||
|
||||
def test_do_node_set_maintenance_off_with_reason_fails(self):
|
||||
client_mock = mock.MagicMock()
|
||||
@ -403,6 +415,26 @@ class NodeShellTest(utils.BaseTestCase):
|
||||
client_mock.node.set_provision_state.assert_called_once_with(
|
||||
'node_uuid', 'provide', configdrive=None)
|
||||
|
||||
def test_do_node_set_console_mode(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.node = 'node_uuid'
|
||||
args.enabled = 'true'
|
||||
|
||||
n_shell.do_node_set_console_mode(client_mock, args)
|
||||
client_mock.node.set_console_mode.assert_called_once_with(
|
||||
'node_uuid', True)
|
||||
|
||||
def test_do_node_set_console_mode_bad(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
args.node = 'node_uuid'
|
||||
args.enabled = 'yuck'
|
||||
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
n_shell.do_node_set_console_mode, client_mock, args)
|
||||
self.assertFalse(client_mock.node.set_console_mode.called)
|
||||
|
||||
def test_do_node_set_boot_device(self):
|
||||
client_mock = mock.MagicMock()
|
||||
args = mock.MagicMock()
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
import os
|
||||
|
||||
from oslo_utils import strutils
|
||||
|
||||
from ironicclient.common import base
|
||||
from ironicclient.common.i18n import _
|
||||
from ironicclient.common import utils
|
||||
@ -39,11 +41,15 @@ class NodeManager(base.Manager):
|
||||
detail=False, sort_key=None, sort_dir=None):
|
||||
"""Retrieve a list of nodes.
|
||||
|
||||
:param associated: Optional, boolean whether to return a list of
|
||||
associated or unassociated nodes.
|
||||
:param maintenance: Optional, boolean value that indicates whether
|
||||
to get nodes in maintenance mode ("True"), or not
|
||||
in maintenance mode ("False").
|
||||
:param associated: Optional. Either a Boolean or a string
|
||||
representation of a Boolean that indicates whether
|
||||
to return a list of associated (True or "True") or
|
||||
unassociated (False or "False") nodes.
|
||||
:param maintenance: Optional. Either a Boolean or a string
|
||||
representation of a Boolean that indicates whether
|
||||
to return nodes in maintenance mode (True or
|
||||
"True"), or not in maintenance mode (False or
|
||||
"False").
|
||||
:param marker: Optional, the UUID of a node, eg the last
|
||||
node from a previous result set. Return
|
||||
the next result set.
|
||||
@ -196,11 +202,32 @@ class NodeManager(base.Manager):
|
||||
_('Unknown HTTP method: %s') % http_method)
|
||||
|
||||
def set_maintenance(self, node_id, state, maint_reason=None):
|
||||
"""Set the maintenance mode for the node.
|
||||
|
||||
:param node_id: The UUID of the node.
|
||||
:param state: the maintenance mode; either a Boolean or a string
|
||||
representation of a Boolean (eg, 'true', 'on', 'false',
|
||||
'off'). True to put the node in maintenance mode; False
|
||||
to take the node out of maintenance mode.
|
||||
:param maint_reason: Optional string. Reason for putting node
|
||||
into maintenance mode.
|
||||
:raises: InvalidAttribute if state is an invalid string (that doesn't
|
||||
represent a Boolean).
|
||||
|
||||
"""
|
||||
if isinstance(state, bool):
|
||||
maintenance_mode = state
|
||||
else:
|
||||
try:
|
||||
maintenance_mode = strutils.bool_from_string(state, True)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidAttribute(_("Argument 'state': %(err)s") %
|
||||
{'err': e})
|
||||
path = "%s/maintenance" % node_id
|
||||
if state in ('true', 'on'):
|
||||
if maintenance_mode:
|
||||
reason = {'reason': maint_reason}
|
||||
return self._update(self._path(path), reason, method='PUT')
|
||||
if state in ('false', 'off'):
|
||||
else:
|
||||
return self._delete(self._path(path))
|
||||
|
||||
def set_power_state(self, node_id, state):
|
||||
@ -241,6 +268,13 @@ class NodeManager(base.Manager):
|
||||
return info.to_dict()
|
||||
|
||||
def set_console_mode(self, node_uuid, enabled):
|
||||
"""Set the console mode for the node.
|
||||
|
||||
:param node_uuid: The UUID of the node.
|
||||
:param enabled: Either a Boolean or a string representation of a
|
||||
Boolean. True to enable the console; False to disable.
|
||||
|
||||
"""
|
||||
path = "%s/states/console" % node_uuid
|
||||
target = {'enabled': enabled}
|
||||
return self._update(self._path(path), target, method='PUT')
|
||||
|
@ -74,12 +74,10 @@ def do_node_show(cc, args):
|
||||
@cliutils.arg(
|
||||
'--maintenance',
|
||||
metavar='<boolean>',
|
||||
choices=['true', 'True', 'false', 'False'],
|
||||
help="List nodes in maintenance mode: 'true' or 'false'.")
|
||||
@cliutils.arg(
|
||||
'--associated',
|
||||
metavar='<boolean>',
|
||||
choices=['true', 'True', 'false', 'False'],
|
||||
help="List nodes by instance association: 'true' or 'false'.")
|
||||
@cliutils.arg(
|
||||
'--detail',
|
||||
@ -91,9 +89,11 @@ def do_node_list(cc, args):
|
||||
"""List the nodes which are registered with the Ironic service."""
|
||||
params = {}
|
||||
if args.associated is not None:
|
||||
params['associated'] = args.associated
|
||||
params['associated'] = utils.bool_argument_value("--associated",
|
||||
args.associated)
|
||||
if args.maintenance is not None:
|
||||
params['maintenance'] = args.maintenance
|
||||
params['maintenance'] = utils.bool_argument_value("--maintenance",
|
||||
args.maintenance)
|
||||
params['detail'] = args.detail
|
||||
|
||||
if args.detail:
|
||||
@ -291,20 +291,21 @@ def do_node_port_list(cc, args):
|
||||
@cliutils.arg(
|
||||
'maintenance_mode',
|
||||
metavar='<maintenance-mode>',
|
||||
choices=['true', 'True', 'false', 'False', 'on', 'off'],
|
||||
help="'true' or 'false'; 'on' or 'off'.")
|
||||
@cliutils.arg(
|
||||
'--reason',
|
||||
metavar='<reason>',
|
||||
default=None,
|
||||
help=('Reason for setting maintenance mode to "true" or "on";'
|
||||
' not valid when setting to "false" or "off".'))
|
||||
help=("Reason for setting maintenance mode to 'true' or 'on';"
|
||||
" not valid when setting to 'false' or 'off'."))
|
||||
def do_node_set_maintenance(cc, args):
|
||||
"""Enable or disable maintenance mode for a node."""
|
||||
if args.reason and args.maintenance_mode.lower() in ('false', 'off'):
|
||||
maintenance_mode = utils.bool_argument_value("<maintenance-mode>",
|
||||
args.maintenance_mode)
|
||||
if args.reason and not maintenance_mode:
|
||||
raise exceptions.CommandError(_('Cannot set "reason" when turning off '
|
||||
'maintenance mode.'))
|
||||
cc.node.set_maintenance(args.node, args.maintenance_mode.lower(),
|
||||
cc.node.set_maintenance(args.node, maintenance_mode,
|
||||
maint_reason=args.reason)
|
||||
|
||||
|
||||
@ -370,12 +371,11 @@ def do_node_get_console(cc, args):
|
||||
@cliutils.arg(
|
||||
'enabled',
|
||||
metavar='<enabled>',
|
||||
choices=['true', 'false'],
|
||||
help="Enable or disable console access for a node. Supported options are: "
|
||||
"'true' or 'false'.")
|
||||
help="Enable or disable console access for a node: 'true' or 'false'.")
|
||||
def do_node_set_console_mode(cc, args):
|
||||
"""Enable or disable serial console access for a node."""
|
||||
cc.node.set_console_mode(args.node, args.enabled)
|
||||
enable = utils.bool_argument_value("<enabled>", args.enabled)
|
||||
cc.node.set_console_mode(args.node, enable)
|
||||
|
||||
|
||||
@cliutils.arg('node', metavar='<node>', help="Name or UUID of the node.")
|
||||
|
Loading…
x
Reference in New Issue
Block a user