From 0fd4c65803ff04a3c5cb9e04076c5d6a9806d918 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Tue, 28 Feb 2023 17:58:24 +0100 Subject: [PATCH] Allow several nodes for most node actions This saves the users from writing scripts with "-f value -c uuid" or similar. It is also faster since OSC initialisation takes significant time (up to several seconds). Change-Id: I8ec6da97dc30d97764655b52b712c95f6c22c76a --- ironicclient/common/utils.py | 2 + ironicclient/osc/v1/baremetal_node.py | 168 ++++---- .../tests/unit/osc/v1/test_baremetal_node.py | 362 ++++++++++-------- ironicclient/tests/unit/v1/test_node.py | 72 ++++ ironicclient/v1/node.py | 100 +++-- .../multinode-actions-9f682ad5172f032f.yaml | 8 + 6 files changed, 453 insertions(+), 259 deletions(-) create mode 100644 releasenotes/notes/multinode-actions-9f682ad5172f032f.yaml diff --git a/ironicclient/common/utils.py b/ironicclient/common/utils.py index 963d2f286..bbb20fe14 100644 --- a/ironicclient/common/utils.py +++ b/ironicclient/common/utils.py @@ -418,6 +418,8 @@ def poll(timeout, poll_interval, poll_delay_function, timeout_message): poll_delay_function(poll_interval) count += 1 + if callable(timeout_message): + timeout_message = timeout_message() raise exc.StateTransitionTimeout(timeout_message) diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py index 7201097a7..7ada12f95 100755 --- a/ironicclient/osc/v1/baremetal_node.py +++ b/ironicclient/osc/v1/baremetal_node.py @@ -61,9 +61,10 @@ class ProvisionStateBaremetalNode(command.Command): parser = super(ProvisionStateBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) parser.add_argument( '--provision-state', @@ -96,13 +97,14 @@ class ProvisionStateBaremetalNode(command.Command): 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, - deploysteps=deploy_steps, - rescue_password=rescue_password) + for node in parsed_args.nodes: + baremetal_client.node.set_provision_state( + node, + parsed_args.provision_state, + configdrive=config_drive, + cleansteps=clean_steps, + deploysteps=deploy_steps, + rescue_password=rescue_password) class ProvisionStateWithWait(ProvisionStateBaremetalNode): @@ -145,11 +147,12 @@ class ProvisionStateWithWait(ProvisionStateBaremetalNode): _("'--wait is not supported for provision state '%s'") % parsed_args.provision_state) - print(_('Waiting for provision state %(state)s on node %(node)s') % - {'state': wait_args['expected_state'], 'node': parsed_args.node}) + print(_('Waiting for provision state %(state)s on node(s) %(node)s') % + {'state': wait_args['expected_state'], + 'node': ', '.join(parsed_args.nodes)}) baremetal_client.node.wait_for_provision_state( - parsed_args.node, + parsed_args.nodes, timeout=parsed_args.wait_timeout, **wait_args) @@ -177,9 +180,10 @@ class BootdeviceSetBaremetalNode(command.Command): parser = super(BootdeviceSetBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node") + nargs='+', + help=_("Names or UUID's of the nodes") ) parser.add_argument( 'device', @@ -200,10 +204,9 @@ class BootdeviceSetBaremetalNode(command.Command): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_boot_device( - parsed_args.node, - parsed_args.device, - parsed_args.persistent) + for node in parsed_args.nodes: + baremetal_client.node.set_boot_device( + node, parsed_args.device, parsed_args.persistent) class BootdeviceShowBaremetalNode(command.ShowOne): @@ -251,9 +254,10 @@ class BootmodeSetBaremetalNode(command.Command): parser = super(BootmodeSetBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) parser.add_argument( 'boot_mode', @@ -268,10 +272,8 @@ class BootmodeSetBaremetalNode(command.Command): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - - baremetal_client.node.set_boot_mode( - parsed_args.node, - parsed_args.boot_mode) + for node in parsed_args.nodes: + baremetal_client.node.set_boot_mode(node, parsed_args.boot_mode) class CleanBaremetalNode(ProvisionStateWithWait): @@ -306,9 +308,10 @@ class ConsoleDisableBaremetalNode(command.Command): parser = super(ConsoleDisableBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node") + nargs='+', + help=_("Names or UUID's of the nodes") ) return parser @@ -316,7 +319,8 @@ class ConsoleDisableBaremetalNode(command.Command): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_console_mode(parsed_args.node, False) + for node in parsed_args.nodes: + baremetal_client.node.set_console_mode(node, False) class ConsoleEnableBaremetalNode(command.Command): @@ -328,9 +332,10 @@ class ConsoleEnableBaremetalNode(command.Command): parser = super(ConsoleEnableBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node") + nargs='+', + help=_("Names or UUID's of the nodes") ) return parser @@ -338,7 +343,8 @@ class ConsoleEnableBaremetalNode(command.Command): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_console_mode(parsed_args.node, True) + for node in parsed_args.nodes: + baremetal_client.node.set_console_mode(node, True) class ConsoleShowBaremetalNode(command.ShowOne): @@ -817,9 +823,10 @@ class MaintenanceSetBaremetalNode(command.Command): parser = super(MaintenanceSetBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) parser.add_argument( '--reason', @@ -834,10 +841,9 @@ class MaintenanceSetBaremetalNode(command.Command): baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_maintenance( - parsed_args.node, - True, - maint_reason=parsed_args.reason) + for node in parsed_args.nodes: + baremetal_client.node.set_maintenance( + node, True, maint_reason=parsed_args.reason) class MaintenanceUnsetBaremetalNode(command.Command): @@ -850,9 +856,10 @@ class MaintenanceUnsetBaremetalNode(command.Command): self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) return parser @@ -861,9 +868,8 @@ class MaintenanceUnsetBaremetalNode(command.Command): baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_maintenance( - parsed_args.node, - False) + for node in parsed_args.nodes: + baremetal_client.node.set_maintenance(node, False) class ManageBaremetalNode(ProvisionStateWithWait): @@ -971,9 +977,10 @@ class PowerBaremetalNode(command.Command): parser = super(PowerBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) parser.add_argument( '--power-timeout', @@ -992,9 +999,10 @@ class PowerBaremetalNode(command.Command): soft = getattr(parsed_args, 'soft', False) - baremetal_client.node.set_power_state( - parsed_args.node, self.POWER_STATE, soft, - timeout=parsed_args.power_timeout) + for node in parsed_args.nodes: + baremetal_client.node.set_power_state( + node, self.POWER_STATE, soft, + timeout=parsed_args.power_timeout) class PowerOffBaremetalNode(PowerBaremetalNode): @@ -1038,9 +1046,10 @@ class RebootBaremetalNode(command.Command): parser = super(RebootBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) parser.add_argument( '--soft', @@ -1065,9 +1074,10 @@ class RebootBaremetalNode(command.Command): baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_power_state( - parsed_args.node, 'reboot', parsed_args.soft, - timeout=parsed_args.power_timeout) + for node in parsed_args.nodes: + baremetal_client.node.set_power_state( + node, 'reboot', parsed_args.soft, + timeout=parsed_args.power_timeout) class RebuildBaremetalNode(ProvisionStateWithWait): @@ -1127,8 +1137,9 @@ class SecurebootOnBaremetalNode(command.Command): parser = super(SecurebootOnBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', + nargs='+', help=_("Name or UUID of the node") ) return parser @@ -1137,7 +1148,8 @@ class SecurebootOnBaremetalNode(command.Command): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_secure_boot(parsed_args.node, 'on') + for node in parsed_args.nodes: + baremetal_client.node.set_secure_boot(node, 'on') class SecurebootOffBaremetalNode(command.Command): @@ -1149,8 +1161,9 @@ class SecurebootOffBaremetalNode(command.Command): parser = super(SecurebootOffBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', + nargs='+', help=_("Name or UUID of the node") ) return parser @@ -1159,7 +1172,8 @@ class SecurebootOffBaremetalNode(command.Command): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - baremetal_client.node.set_secure_boot(parsed_args.node, 'off') + for node in parsed_args.nodes: + baremetal_client.node.set_secure_boot(node, 'off') class SetBaremetalNode(command.Command): @@ -1184,9 +1198,10 @@ class SetBaremetalNode(command.Command): parser = super(SetBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node."), + nargs='+', + help=_("Names or UUID's of the nodes."), ) parser.add_argument( "--instance-uuid", @@ -1389,6 +1404,13 @@ class SetBaremetalNode(command.Command): def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) + if parsed_args.name and len(parsed_args.nodes) > 1: + raise exc.CommandError( + _("--name cannot be used with more than one node")) + if parsed_args.instance_uuid and len(parsed_args.nodes) > 1: + raise exc.CommandError( + _("--instance-uuid cannot be used with more than one node")) + baremetal_client = self.app.client_manager.baremetal # NOTE(rloo): Do this before updating the rest. Otherwise, it won't @@ -1398,8 +1420,8 @@ class SetBaremetalNode(command.Command): raid_config = parsed_args.target_raid_config raid_config = utils.handle_json_arg(raid_config, 'target_raid_config') - baremetal_client.node.set_target_raid_config(parsed_args.node, - raid_config) + for node in parsed_args.nodes: + baremetal_client.node.set_target_raid_config(node, raid_config) properties = [] for field in ['instance_uuid', 'name', @@ -1451,9 +1473,10 @@ class SetBaremetalNode(command.Command): properties.extend(utils.args_array_to_patch('add', network_data)) if properties: - baremetal_client.node.update( - parsed_args.node, properties, - reset_interfaces=parsed_args.reset_interfaces) + for node in parsed_args.nodes: + baremetal_client.node.update( + node, properties, + reset_interfaces=parsed_args.reset_interfaces) elif not parsed_args.target_raid_config: self.log.warning("Please specify what to set.") @@ -1534,9 +1557,10 @@ class UnsetBaremetalNode(command.Command): parser = super(UnsetBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) parser.add_argument( '--instance-uuid', @@ -1732,7 +1756,8 @@ class UnsetBaremetalNode(command.Command): # work if parsed_args.node is the name and the name is # also being removed. if parsed_args.target_raid_config: - baremetal_client.node.set_target_raid_config(parsed_args.node, {}) + for node in parsed_args.nodes: + baremetal_client.node.set_target_raid_config(node, {}) properties = [] for field in ['instance_uuid', 'name', 'chassis_uuid', @@ -1765,8 +1790,10 @@ class UnsetBaremetalNode(command.Command): if parsed_args.network_data: properties.extend(utils.args_array_to_patch( 'remove', ["network_data"])) + if properties: - baremetal_client.node.update(parsed_args.node, properties) + for node in parsed_args.nodes: + baremetal_client.node.update(node, properties) elif not parsed_args.target_raid_config: self.log.warning("Please specify what to unset.") @@ -1912,9 +1939,10 @@ class InjectNmiBaremetalNode(command.Command): parser = super(InjectNmiBaremetalNode, self).get_parser(prog_name) parser.add_argument( - 'node', + 'nodes', metavar='', - help=_("Name or UUID of the node.") + nargs='+', + help=_("Names or UUID's of the nodes.") ) return parser @@ -1923,8 +1951,8 @@ class InjectNmiBaremetalNode(command.Command): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal - - baremetal_client.node.inject_nmi(parsed_args.node) + for node in parsed_args.nodes: + baremetal_client.node.inject_nmi(node) class ListTraitsBaremetalNode(command.Lister): diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py index 6426eef1e..8771f3a42 100644 --- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py @@ -47,7 +47,7 @@ class TestAbort(TestBaremetal): def test_abort(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'abort'), ] @@ -70,7 +70,7 @@ class TestAdopt(TestBaremetal): def test_adopt(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'adopt'), ] @@ -82,25 +82,13 @@ class TestAdopt(TestBaremetal): 'node_uuid', 'adopt', cleansteps=None, deploysteps=None, configdrive=None, rescue_password=None) - - def test_adopt_no_wait(self): - arglist = ['node_uuid'] - verifylist = [ - ('node', 'node_uuid'), - ('provision_state', 'adopt') - ] - - 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_adopt_baremetal_provision_state_active_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'adopt'), ('wait_timeout', 15) ] @@ -110,15 +98,19 @@ class TestAdopt(TestBaremetal): self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node + test_node.set_provision_state.assert_called_once_with( + 'node_uuid', 'adopt', + cleansteps=None, deploysteps=None, configdrive=None, + rescue_password=None) test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['node_uuid'], expected_state='active', poll_interval=2, timeout=15) def test_adopt_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'adopt'), ('wait_timeout', 0) ] @@ -128,8 +120,12 @@ class TestAdopt(TestBaremetal): self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node + test_node.set_provision_state.assert_called_once_with( + 'node_uuid', 'adopt', + cleansteps=None, deploysteps=None, configdrive=None, + rescue_password=None) test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['node_uuid'], expected_state='active', poll_interval=2, timeout=0) @@ -143,7 +139,7 @@ class TestClean(TestBaremetal): def test_clean_without_steps(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'clean'), ] @@ -167,7 +163,7 @@ class TestClean(TestBaremetal): verifylist = [ ('clean_steps', steps_json), ('provision_state', 'clean'), - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -189,7 +185,7 @@ class TestInspect(TestBaremetal): def test_inspect(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'inspect'), ] @@ -212,7 +208,7 @@ class TestManage(TestBaremetal): def test_manage(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'manage'), ] @@ -235,7 +231,7 @@ class TestProvide(TestBaremetal): def test_provide(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'provide'), ] @@ -258,7 +254,7 @@ class TestRebuild(TestBaremetal): def test_rebuild(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rebuild'), ] @@ -281,7 +277,7 @@ class TestUndeploy(TestBaremetal): def test_undeploy(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'deleted'), ] @@ -303,7 +299,7 @@ class TestBootdeviceSet(TestBaremetal): def test_bootdevice_set(self): arglist = ['node_uuid', 'bios'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('device', 'bios')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -315,7 +311,7 @@ class TestBootdeviceSet(TestBaremetal): def test_bootdevice_set_persistent(self): arglist = ['node_uuid', 'bios', '--persistent'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('device', 'bios'), ('persistent', True)] @@ -328,7 +324,7 @@ class TestBootdeviceSet(TestBaremetal): def test_bootdevice_set_invalid_device(self): arglist = ['node_uuid', 'foo'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('device', 'foo')] self.assertRaises(oscutils.ParserException, @@ -389,7 +385,7 @@ class TestConsoleDisable(TestBaremetal): def test_console_disable(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -408,7 +404,7 @@ class TestConsoleEnable(TestBaremetal): def test_console_enable(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -449,7 +445,7 @@ class TestSecurebootOff(TestBaremetal): def test_secure_boot_off(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -468,7 +464,7 @@ class TestSecurebootOn(TestBaremetal): def test_console_enable(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -486,10 +482,9 @@ class TestBootmodeSet(TestBaremetal): self.cmd = baremetal_node.BootmodeSetBaremetalNode(self.app, None) def test_baremetal_boot_mode_bios(self): - arglist = ['node_uuid', - 'bios'] + arglist = ['node_uuid', 'bios'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('boot_mode', 'bios'), ] @@ -1445,7 +1440,7 @@ class TestBaremetalMaintenanceSet(TestBaremetal): arglist = ['node_uuid', '--reason', 'maintenance reason'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('reason', 'maintenance reason'), ] @@ -1462,7 +1457,7 @@ class TestBaremetalMaintenanceSet(TestBaremetal): def test_baremetal_maintenance_on_no_reason(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1475,6 +1470,20 @@ class TestBaremetalMaintenanceSet(TestBaremetal): maint_reason=None ) + def test_baremetal_maintenance_on_several_nodes(self): + arglist = ['node_uuid', 'node_name'] + verifylist = [ + ('nodes', ['node_uuid', 'node_name']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.set_maintenance.assert_has_calls( + [mock.call(n, True, maint_reason=None) for n in arglist] + ) + class TestBaremetalMaintenanceUnset(TestBaremetal): def setUp(self): @@ -1485,7 +1494,7 @@ class TestBaremetalMaintenanceUnset(TestBaremetal): def test_baremetal_maintenance_off(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1495,6 +1504,18 @@ class TestBaremetalMaintenanceUnset(TestBaremetal): 'node_uuid', False) + def test_baremetal_maintenance_off_several_nodes(self): + arglist = ['node_uuid', 'node_name'] + verifylist = [('nodes', ['node_uuid', 'node_name'])] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.set_maintenance.assert_has_calls( + [mock.call(n, False) for n in arglist] + ) + class TestPassthruCall(TestBaremetal): def setUp(self): @@ -1572,7 +1593,7 @@ class TestPower(TestBaremetal): def test_baremetal_power(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1590,7 +1611,7 @@ class TestPowerOff(TestBaremetal): def test_baremetal_power_off(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', False), ('power_timeout', None)] @@ -1603,7 +1624,7 @@ class TestPowerOff(TestBaremetal): def test_baremetal_power_off_timeout(self): arglist = ['node_uuid', '--power-timeout', '2'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', False), ('power_timeout', 2)] @@ -1616,7 +1637,7 @@ class TestPowerOff(TestBaremetal): def test_baremetal_soft_power_off(self): arglist = ['node_uuid', '--soft'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', True), ('power_timeout', None)] @@ -1629,7 +1650,7 @@ class TestPowerOff(TestBaremetal): def test_baremetal_soft_power_off_timeout(self): arglist = ['node_uuid', '--soft', '--power-timeout', '2'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', True), ('power_timeout', 2)] @@ -1648,6 +1669,20 @@ class TestPowerOff(TestBaremetal): self.check_parser, self.cmd, arglist, verifylist) + def test_baremetal_power_off_several_nodes(self): + arglist = ['node_uuid', 'node_name'] + verifylist = [('nodes', ['node_uuid', 'node_name']), + ('soft', False), + ('power_timeout', None)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.baremetal_mock.node.set_power_state.assert_has_calls([ + mock.call(n, 'off', False, timeout=None) for n in arglist + ]) + class TestPowerOn(TestBaremetal): def setUp(self): @@ -1658,7 +1693,7 @@ class TestPowerOn(TestBaremetal): def test_baremetal_power_on(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('power_timeout', None)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1670,7 +1705,7 @@ class TestPowerOn(TestBaremetal): def test_baremetal_power_on_timeout(self): arglist = ['node_uuid', '--power-timeout', '2'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('power_timeout', 2)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1701,7 +1736,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal): '--config-drive', 'path/to/drive', '--deploy-steps', '[{"interface":"deploy"}]'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'active'), ('config_drive', 'path/to/drive'), ('deploy_steps', '[{"interface":"deploy"}]') @@ -1721,7 +1756,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--config-drive', '{"meta_data": {}}'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'active'), ('config_drive', '{"meta_data": {}}'), ] @@ -1738,7 +1773,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal): def test_deploy_no_wait(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'active') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1749,7 +1784,7 @@ class TestDeployBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'active'), ('wait_timeout', 15) ] @@ -1760,14 +1795,14 @@ class TestDeployBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['node_uuid'], expected_state='active', poll_interval=10, timeout=15) def test_deploy_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'active'), ('wait_timeout', 0) ] @@ -1778,14 +1813,37 @@ class TestDeployBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['node_uuid'], expected_state='active', poll_interval=10, timeout=0) + def test_deploy_baremetal_provision_state_several_nodes(self): + arglist = ['node_uuid', 'node_name', + '--wait', '15'] + verifylist = [ + ('nodes', ['node_uuid', 'node_name']), + ('provision_state', 'active'), + ('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.set_provision_state.assert_has_calls([ + mock.call(n, 'active', cleansteps=None, deploysteps=None, + configdrive=None, rescue_password=None) + for n in ['node_uuid', 'node_name'] + ]) + test_node.wait_for_provision_state.assert_called_once_with( + ['node_uuid', 'node_name'], expected_state='active', + poll_interval=10, timeout=15) + def test_deploy_baremetal_provision_state_mismatch(self): arglist = ['node_uuid', '--provision-state', 'abort'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'active'), ] @@ -1804,7 +1862,7 @@ class TestManageBaremetalProvisionState(TestBaremetal): def test_manage_no_wait(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'manage') ] @@ -1818,7 +1876,7 @@ class TestManageBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'manage'), ('wait_timeout', 15) ] @@ -1829,14 +1887,14 @@ class TestManageBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='manageable', + ['node_uuid'], expected_state='manageable', poll_interval=2, timeout=15) def test_manage_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'manage'), ('wait_timeout', 0) ] @@ -1847,7 +1905,7 @@ class TestManageBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='manageable', + ['node_uuid'], expected_state='manageable', poll_interval=2, timeout=0) @@ -1861,7 +1919,7 @@ class TestCleanBaremetalProvisionState(TestBaremetal): def test_clean_no_wait(self): arglist = ['node_uuid', '--clean-steps', '-'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'clean'), ('clean_steps', '-') ] @@ -1877,7 +1935,7 @@ class TestCleanBaremetalProvisionState(TestBaremetal): '--wait', '15', '--clean-steps', '-'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'clean'), ('wait_timeout', 15), ('clean_steps', '-') @@ -1889,7 +1947,7 @@ class TestCleanBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='manageable', + ['node_uuid'], expected_state='manageable', poll_interval=10, timeout=15) def test_clean_baremetal_provision_state_default_wait(self): @@ -1897,7 +1955,7 @@ class TestCleanBaremetalProvisionState(TestBaremetal): '--wait', '--clean-steps', '-'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'clean'), ('wait_timeout', 0), ('clean_steps', '-') @@ -1909,7 +1967,7 @@ class TestCleanBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='manageable', + ['node_uuid'], expected_state='manageable', poll_interval=10, timeout=0) @@ -1924,7 +1982,7 @@ class TestRescueBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--rescue-password', 'supersecret'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rescue'), ('rescue_password', 'supersecret'), ] @@ -1942,7 +2000,7 @@ class TestRescueBaremetalProvisionState(TestBaremetal): '--wait', '15', '--rescue-password', 'supersecret'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rescue'), ('rescue_password', 'supersecret'), ('wait_timeout', 15) @@ -1954,7 +2012,7 @@ class TestRescueBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='rescue', + ['node_uuid'], expected_state='rescue', poll_interval=10, timeout=15) def test_rescue_baremetal_provision_state_default_wait(self): @@ -1962,7 +2020,7 @@ class TestRescueBaremetalProvisionState(TestBaremetal): '--wait', '--rescue-password', 'supersecret'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rescue'), ('rescue_password', 'supersecret'), ('wait_timeout', 0) @@ -1974,7 +2032,7 @@ class TestRescueBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='rescue', + ['node_uuid'], expected_state='rescue', poll_interval=10, timeout=0) def test_rescue_baremetal_no_rescue_password(self): @@ -1997,7 +2055,7 @@ class TestInspectBaremetalProvisionState(TestBaremetal): def test_inspect_no_wait(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'inspect') ] @@ -2011,7 +2069,7 @@ class TestInspectBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'inspect'), ('wait_timeout', 15) ] @@ -2022,14 +2080,14 @@ class TestInspectBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='manageable', + ['node_uuid'], expected_state='manageable', poll_interval=2, timeout=15) def test_inspect_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'inspect'), ('wait_timeout', 0) ] @@ -2040,7 +2098,7 @@ class TestInspectBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='manageable', + ['node_uuid'], expected_state='manageable', poll_interval=2, timeout=0) @@ -2054,7 +2112,7 @@ class TestProvideBaremetalProvisionState(TestBaremetal): def test_provide_no_wait(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'provide') ] @@ -2068,7 +2126,7 @@ class TestProvideBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'provide'), ('wait_timeout', 15) ] @@ -2079,14 +2137,14 @@ class TestProvideBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='available', + ['node_uuid'], expected_state='available', poll_interval=10, timeout=15) def test_provide_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'provide'), ('wait_timeout', 0) ] @@ -2097,7 +2155,7 @@ class TestProvideBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='available', + ['node_uuid'], expected_state='available', poll_interval=10, timeout=0) @@ -2113,7 +2171,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): '--config-drive', 'path/to/drive', '--deploy-steps', '[{"interface":"deploy"}]'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rebuild'), ('config_drive', 'path/to/drive'), ('deploy_steps', '[{"interface":"deploy"}]') @@ -2131,7 +2189,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): def test_rebuild_no_wait(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rebuild') ] @@ -2150,7 +2208,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rebuild'), ('wait_timeout', 15) ] @@ -2161,14 +2219,14 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['node_uuid'], expected_state='active', poll_interval=10, timeout=15) def test_rebuild_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'rebuild'), ('wait_timeout', 0) ] @@ -2179,7 +2237,7 @@ class TestRebuildBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['node_uuid'], expected_state='active', poll_interval=10, timeout=0) @@ -2193,7 +2251,7 @@ class TestUndeployBaremetalProvisionState(TestBaremetal): def test_undeploy_no_wait(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'deleted') ] @@ -2207,7 +2265,7 @@ class TestUndeployBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'deleted'), ('wait_timeout', 15) ] @@ -2218,14 +2276,14 @@ class TestUndeployBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='available', + ['node_uuid'], expected_state='available', poll_interval=10, timeout=15) def test_undeploy_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'deleted'), ('wait_timeout', 0) ] @@ -2236,7 +2294,7 @@ class TestUndeployBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='available', + ['node_uuid'], expected_state='available', poll_interval=10, timeout=0) @@ -2250,7 +2308,7 @@ class TestUnrescueBaremetalProvisionState(TestBaremetal): def test_unrescue_no_wait(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'unrescue'), ] @@ -2266,7 +2324,7 @@ class TestUnrescueBaremetalProvisionState(TestBaremetal): arglist = ['node_uuid', '--wait', '15'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('provision_state', 'unrescue'), ('wait_timeout', 15) ] @@ -2277,14 +2335,14 @@ class TestUnrescueBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['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'), + ('nodes', ['node_uuid']), ('provision_state', 'unrescue'), ('wait_timeout', 0) ] @@ -2295,7 +2353,7 @@ class TestUnrescueBaremetalProvisionState(TestBaremetal): test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( - 'node_uuid', expected_state='active', + ['node_uuid'], expected_state='active', poll_interval=10, timeout=0) @@ -2316,7 +2374,7 @@ class TestBaremetalReboot(TestBaremetal): def test_baremetal_reboot_uuid_only(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', False), ('power_timeout', None)] @@ -2329,7 +2387,7 @@ class TestBaremetalReboot(TestBaremetal): def test_baremetal_reboot_timeout(self): arglist = ['node_uuid', '--power-timeout', '2'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', False), ('power_timeout', 2)] @@ -2342,7 +2400,7 @@ class TestBaremetalReboot(TestBaremetal): def test_baremetal_soft_reboot(self): arglist = ['node_uuid', '--soft'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', True), ('power_timeout', None)] @@ -2355,7 +2413,7 @@ class TestBaremetalReboot(TestBaremetal): def test_baremetal_soft_reboot_timeout(self): arglist = ['node_uuid', '--soft', '--power-timeout', '2'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('soft', True), ('power_timeout', 2)] @@ -2392,7 +2450,7 @@ class TestBaremetalSet(TestBaremetal): def test_baremetal_set_no_property(self): arglist = ['node_uuid'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -2402,7 +2460,7 @@ class TestBaremetalSet(TestBaremetal): def test_baremetal_set_one_property(self): arglist = ['node_uuid', '--property', 'path/to/property=value'] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('property', ['path/to/property=value']), ] @@ -2424,7 +2482,7 @@ class TestBaremetalSet(TestBaremetal): '--property', 'other/path=value2' ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('property', [ 'path/to/property=value', @@ -2453,7 +2511,7 @@ class TestBaremetalSet(TestBaremetal): '--instance-uuid', 'xxxxx', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('instance_uuid', 'xxxxx') ] @@ -2473,7 +2531,7 @@ class TestBaremetalSet(TestBaremetal): '--name', 'xxxxx', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('name', 'xxxxx') ] @@ -2494,7 +2552,7 @@ class TestBaremetalSet(TestBaremetal): '--chassis-uuid', chassis, ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('chassis_uuid', chassis) ] @@ -2514,7 +2572,7 @@ class TestBaremetalSet(TestBaremetal): '--driver', 'xxxxx', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('driver', 'xxxxx') ] @@ -2535,7 +2593,7 @@ class TestBaremetalSet(TestBaremetal): '--reset-interfaces', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('driver', 'xxxxx'), ('reset_interfaces', True), ] @@ -2556,7 +2614,7 @@ class TestBaremetalSet(TestBaremetal): '--reset-interfaces', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('reset_interfaces', True), ] @@ -2572,7 +2630,7 @@ class TestBaremetalSet(TestBaremetal): '--%s-interface' % interface, 'xxxxx', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('%s_interface' % interface, 'xxxxx') ] @@ -2629,7 +2687,7 @@ class TestBaremetalSet(TestBaremetal): '--reset-%s-interface' % interface, ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('reset_%s_interface' % interface, True) ] @@ -2685,7 +2743,7 @@ class TestBaremetalSet(TestBaremetal): '--resource-class', 'foo', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('resource_class', 'foo') ] @@ -2705,7 +2763,7 @@ class TestBaremetalSet(TestBaremetal): '--conductor-group', 'foo', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('conductor_group', 'foo') ] @@ -2725,7 +2783,7 @@ class TestBaremetalSet(TestBaremetal): '--automated-clean' ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('automated_clean', True) ] @@ -2745,7 +2803,7 @@ class TestBaremetalSet(TestBaremetal): '--no-automated-clean' ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('automated_clean', False) ] @@ -2765,7 +2823,7 @@ class TestBaremetalSet(TestBaremetal): '--protected' ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('protected', True) ] @@ -2786,7 +2844,7 @@ class TestBaremetalSet(TestBaremetal): '--protected-reason', 'reason!' ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('protected', True), ('protected_reason', 'reason!') ] @@ -2808,7 +2866,7 @@ class TestBaremetalSet(TestBaremetal): '--retired' ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('retired', True) ] @@ -2829,7 +2887,7 @@ class TestBaremetalSet(TestBaremetal): '--retired-reason', 'out of warranty!' ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('retired', True), ('retired_reason', 'out of warranty!') ] @@ -2852,7 +2910,7 @@ class TestBaremetalSet(TestBaremetal): '--extra', 'foo=bar', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('extra', ['foo=bar']) ] @@ -2872,7 +2930,7 @@ class TestBaremetalSet(TestBaremetal): '--driver-info', 'foo=bar', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('driver_info', ['foo=bar']) ] @@ -2892,7 +2950,7 @@ class TestBaremetalSet(TestBaremetal): '--instance-info', 'foo=bar', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('instance_info', ['foo=bar']) ] @@ -2916,7 +2974,7 @@ class TestBaremetalSet(TestBaremetal): arglist = ['node_uuid', '--target-raid-config', target_raid_config_string] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('target_raid_config', target_raid_config_string)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -2941,7 +2999,7 @@ class TestBaremetalSet(TestBaremetal): arglist = ['node_uuid', '--name', 'xxxxx', '--target-raid-config', target_raid_config_string] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('name', 'xxxxx'), ('target_raid_config', target_raid_config_string)] @@ -2971,7 +3029,7 @@ class TestBaremetalSet(TestBaremetal): arglist = ['node_uuid', '--target-raid-config', target_value] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('target_raid_config', target_value)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -2994,7 +3052,7 @@ class TestBaremetalSet(TestBaremetal): arglist = ['node_uuid', '--target-raid-config', target_value] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('target_raid_config', target_value)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -3014,7 +3072,7 @@ class TestBaremetalSet(TestBaremetal): '--owner', 'owner 1', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('owner', 'owner 1') ] @@ -3036,7 +3094,7 @@ class TestBaremetalSet(TestBaremetal): '--description', 'there is no spoon', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('description', 'there is no spoon') ] @@ -3058,7 +3116,7 @@ class TestBaremetalSet(TestBaremetal): '--lessee', 'lessee 1', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('lessee', 'lessee 1') ] @@ -3083,7 +3141,7 @@ class TestBaremetalSet(TestBaremetal): arglist = ['node_uuid', '--network-data', network_data_string] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('network_data', network_data_string)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -3273,7 +3331,7 @@ class TestBaremetalUnset(TestBaremetal): def test_baremetal_unset_no_property(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) @@ -3281,7 +3339,7 @@ class TestBaremetalUnset(TestBaremetal): def test_baremetal_unset_one_property(self): arglist = ['node_uuid', '--property', 'path/to/property'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('property', ['path/to/property'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -3296,7 +3354,7 @@ class TestBaremetalUnset(TestBaremetal): arglist = ['node_uuid', '--property', 'path/to/property', '--property', 'other/path'] - verifylist = [('node', 'node_uuid'), + verifylist = [('nodes', ['node_uuid']), ('property', ['path/to/property', 'other/path'])] @@ -3317,7 +3375,7 @@ class TestBaremetalUnset(TestBaremetal): '--instance-uuid', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('instance_uuid', True) ] @@ -3336,7 +3394,7 @@ class TestBaremetalUnset(TestBaremetal): '--name', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('name', True) ] @@ -3355,7 +3413,7 @@ class TestBaremetalUnset(TestBaremetal): '--resource-class', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('resource_class', True) ] @@ -3374,7 +3432,7 @@ class TestBaremetalUnset(TestBaremetal): '--conductor-group', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('conductor_group', True) ] @@ -3393,7 +3451,7 @@ class TestBaremetalUnset(TestBaremetal): '--automated-clean', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('automated_clean', True) ] @@ -3412,7 +3470,7 @@ class TestBaremetalUnset(TestBaremetal): '--protected', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('protected', True) ] @@ -3431,7 +3489,7 @@ class TestBaremetalUnset(TestBaremetal): '--protected-reason', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('protected_reason', True) ] @@ -3450,7 +3508,7 @@ class TestBaremetalUnset(TestBaremetal): '--retired', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('retired', True) ] @@ -3469,7 +3527,7 @@ class TestBaremetalUnset(TestBaremetal): '--retired-reason', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('retired_reason', True) ] @@ -3488,7 +3546,7 @@ class TestBaremetalUnset(TestBaremetal): '--extra', 'foo', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('extra', ['foo']) ] @@ -3507,7 +3565,7 @@ class TestBaremetalUnset(TestBaremetal): '--driver-info', 'foo', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('driver_info', ['foo']) ] @@ -3526,7 +3584,7 @@ class TestBaremetalUnset(TestBaremetal): '--instance-info', 'foo', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('instance_info', ['foo']) ] @@ -3546,7 +3604,7 @@ class TestBaremetalUnset(TestBaremetal): '--target-raid-config', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('target_raid_config', True) ] @@ -3567,7 +3625,7 @@ class TestBaremetalUnset(TestBaremetal): '--target-raid-config', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('name', True), ('target_raid_config', True) ] @@ -3590,7 +3648,7 @@ class TestBaremetalUnset(TestBaremetal): '--chassis-uuid', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('chassis_uuid', True) ] @@ -3609,7 +3667,7 @@ class TestBaremetalUnset(TestBaremetal): '--%s-interface' % interface, ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('%s_interface' % interface, True) ] @@ -3664,7 +3722,7 @@ class TestBaremetalUnset(TestBaremetal): '--owner', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('owner', True) ] @@ -3683,7 +3741,7 @@ class TestBaremetalUnset(TestBaremetal): '--description', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('description', True) ] @@ -3702,7 +3760,7 @@ class TestBaremetalUnset(TestBaremetal): '--lessee', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('lessee', True) ] @@ -3721,7 +3779,7 @@ class TestBaremetalUnset(TestBaremetal): '--network-data', ] verifylist = [ - ('node', 'node_uuid'), + ('nodes', ['node_uuid']), ('network_data', True) ] @@ -3883,7 +3941,7 @@ class TestBaremetalInject(TestBaremetal): def test_baremetal_inject_nmi_uuid(self): arglist = ['node_uuid'] - verifylist = [('node', 'node_uuid')] + verifylist = [('nodes', ['node_uuid'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/ironicclient/tests/unit/v1/test_node.py b/ironicclient/tests/unit/v1/test_node.py index 83808d569..2a6d4abe6 100644 --- a/ironicclient/tests/unit/v1/test_node.py +++ b/ironicclient/tests/unit/v1/test_node.py @@ -2014,6 +2014,78 @@ class NodeManagerTest(testtools.TestCase): mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL) self.assertEqual(3, mock_sleep.call_count) + @mock.patch.object(time, 'sleep', autospec=True) + @mock.patch.object(node.NodeManager, 'get', autospec=True) + def test_wait_for_provision_state_several(self, mock_get, mock_sleep): + mock_get.side_effect = [ + self._fake_node_for_wait('deploying', target='active'), + # Sometimes non-fatal errors can be recorded in last_error + self._fake_node_for_wait('deploying', target='active', + error='Node locked'), + self._fake_node_for_wait('deploying', target='active'), + self._fake_node_for_wait('deploying', target='active'), + self._fake_node_for_wait('active'), + self._fake_node_for_wait('active'), + ] + + self.mgr.wait_for_provision_state(['node1', 'node2'], 'active') + + mock_get.assert_has_calls([ + mock.call(self.mgr, 'node1', os_ironic_api_version=None, + global_request_id=None), + mock.call(self.mgr, 'node2', os_ironic_api_version=None, + global_request_id=None), + ], any_order=True) + self.assertEqual(6, mock_get.call_count) + mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL) + self.assertEqual(2, mock_sleep.call_count) + + @mock.patch.object(time, 'sleep', autospec=True) + @mock.patch.object(node.NodeManager, 'get', autospec=True) + def test_wait_for_provision_state_one_failed(self, mock_get, mock_sleep): + mock_get.side_effect = [ + self._fake_node_for_wait('deploying', target='active'), + self._fake_node_for_wait('deploying', target='active'), + self._fake_node_for_wait('active'), + self._fake_node_for_wait('deploy failed', error='boom'), + ] + + self.assertRaisesRegex(exc.StateTransitionFailed, + 'boom', + self.mgr.wait_for_provision_state, + ['node1', 'node2'], 'active') + + mock_get.assert_has_calls([ + mock.call(self.mgr, 'node1', os_ironic_api_version=None, + global_request_id=None), + mock.call(self.mgr, 'node2', os_ironic_api_version=None, + global_request_id=None), + ], any_order=True) + self.assertEqual(4, mock_get.call_count) + mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL) + self.assertEqual(1, mock_sleep.call_count) + + @mock.patch.object(time, 'sleep', autospec=True) + @mock.patch.object(node.NodeManager, 'get', autospec=True) + def test_wait_for_provision_state_one_timeout(self, mock_get, mock_sleep): + fake_waiting_node = self._fake_node_for_wait( + 'deploying', target='active') + fake_success_node = self._fake_node_for_wait('active') + + def side_effect(node_manager, node_ident, *args, **kwargs): + if node_ident == 'node1': + return fake_success_node + else: + return fake_waiting_node + + mock_get.side_effect = side_effect + + self.assertRaisesRegex(exc.StateTransitionTimeout, + r'Node\(s\) node2', + self.mgr.wait_for_provision_state, + ['node1', 'node2'], 'active', + timeout=0.001) + def test_node_get_traits(self): traits = self.mgr.get_traits(NODE1['uuid']) expect = [ diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py index 80a32c899..a8af62da6 100644 --- a/ironicclient/v1/node.py +++ b/ironicclient/v1/node.py @@ -940,6 +940,43 @@ class NodeManager(base.CreateManager): os_ironic_api_version=os_ironic_api_version, global_request_id=global_request_id) + def _check_one_provision_state(self, node_ident, expected_state, + fail_on_unexpected_state=True, + os_ironic_api_version=None, + global_request_id=None): + # TODO(dtantsur): use version negotiation to request API 1.8 and use + # the "fields" argument to reduce amount of data sent. + node = self.get( + node_ident, os_ironic_api_version=os_ironic_api_version, + global_request_id=global_request_id) + if node.provision_state == expected_state: + LOG.debug('Node %(node)s reached provision state %(state)s', + {'node': node_ident, 'state': expected_state}) + return True + + # Note that if expected_state == 'error' we still succeed + if (node.provision_state == 'error' + or node.provision_state.endswith(' failed')): + raise exc.StateTransitionFailed( + _('Node %(node)s failed to reach state %(state)s. ' + 'It\'s in state %(actual)s, and has error: %(error)s') % + {'node': node_ident, 'state': expected_state, + 'actual': node.provision_state, 'error': node.last_error}) + + if fail_on_unexpected_state and not node.target_provision_state: + raise exc.StateTransitionFailed( + _('Node %(node)s failed to reach state %(state)s. ' + 'It\'s in unexpected stable state %(actual)s') % + {'node': node_ident, 'state': expected_state, + 'actual': node.provision_state}) + + LOG.debug('Still waiting for node %(node)s to reach state ' + '%(state)s, the current state is %(actual)s', + {'node': node_ident, 'state': expected_state, + 'actual': node.provision_state}) + + return False + def wait_for_provision_state(self, node_ident, expected_state, timeout=0, poll_interval=_DEFAULT_POLL_INTERVAL, @@ -947,7 +984,7 @@ class NodeManager(base.CreateManager): fail_on_unexpected_state=True, os_ironic_api_version=None, global_request_id=None): - """Helper function to wait for a node to reach a given state. + """Helper function to wait for nodes to reach a given state. Polls Ironic API in a loop until node gets to a requested state. @@ -957,7 +994,7 @@ class NodeManager(base.CreateManager): * Unexpected stable state is reached and fail_on_unexpected_state is on * Error state is reached (if it's not equal to expected_state) - :param node_ident: node UUID or name + :param node_ident: node UUID or name (one or a list) :param expected_state: expected final provision state :param timeout: timeout in seconds, no timeout if 0 :param poll_interval: interval in seconds between 2 poll @@ -975,43 +1012,32 @@ class NodeManager(base.CreateManager): :raises: StateTransitionTimeout on timeout """ expected_state = expected_state.lower() - timeout_msg = _('Node %(node)s failed to reach state %(state)s in ' - '%(timeout)s seconds') % {'node': node_ident, - 'state': expected_state, - 'timeout': timeout} + if not isinstance(node_ident, list): + node_ident = [node_ident] + unfinished = node_ident + + def _timeout(): + return ( + _('Node(s) %(node)s failed to reach state %(state)s in ' + '%(timeout)s seconds') + % {'node': ', '.join(unfinished), + 'state': expected_state, + 'timeout': timeout} + ) - # TODO(dtantsur): use version negotiation to request API 1.8 and use - # the "fields" argument to reduce amount of data sent. for _count in utils.poll(timeout, poll_interval, poll_delay_function, - timeout_msg): - node = self.get( - node_ident, os_ironic_api_version=os_ironic_api_version, - global_request_id=global_request_id) - if node.provision_state == expected_state: - LOG.debug('Node %(node)s reached provision state %(state)s', - {'node': node_ident, 'state': expected_state}) - return - - # Note that if expected_state == 'error' we still succeed - if (node.provision_state == 'error' - or node.provision_state.endswith(' failed')): - raise exc.StateTransitionFailed( - _('Node %(node)s failed to reach state %(state)s. ' - 'It\'s in state %(actual)s, and has error: %(error)s') % - {'node': node_ident, 'state': expected_state, - 'actual': node.provision_state, 'error': node.last_error}) - - if fail_on_unexpected_state and not node.target_provision_state: - raise exc.StateTransitionFailed( - _('Node %(node)s failed to reach state %(state)s. ' - 'It\'s in unexpected stable state %(actual)s') % - {'node': node_ident, 'state': expected_state, - 'actual': node.provision_state}) - - LOG.debug('Still waiting for node %(node)s to reach state ' - '%(state)s, the current state is %(actual)s', - {'node': node_ident, 'state': expected_state, - 'actual': node.provision_state}) + _timeout): + current, unfinished = unfinished, [] + for node in current: + if not self._check_one_provision_state( + node, + expected_state, + fail_on_unexpected_state=fail_on_unexpected_state, + os_ironic_api_version=os_ironic_api_version, + global_request_id=global_request_id): + unfinished.append(node) + if not unfinished: + break def get_history_list(self, node_ident, diff --git a/releasenotes/notes/multinode-actions-9f682ad5172f032f.yaml b/releasenotes/notes/multinode-actions-9f682ad5172f032f.yaml new file mode 100644 index 000000000..b6c8bef16 --- /dev/null +++ b/releasenotes/notes/multinode-actions-9f682ad5172f032f.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Most of the node action commands now support providing several nodes. + The nodes are processed sequentially, the process is stopped on first + failure. + - | + The ``wait_for_provision_state`` Python call now supports several nodes.