diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 28c28b1944..21a21974a1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2218,24 +2218,49 @@ class DeleteServer(command.Command): self.app.stdout.flush() compute_client = self.app.client_manager.compute + + deleted_servers = [] for server in parsed_args.server: - server_obj = compute_client.find_server( - server, - ignore_missing=False, - all_projects=parsed_args.all_projects, - ) + try: + server_obj = compute_client.find_server( + server, + ignore_missing=False, + all_projects=parsed_args.all_projects, + ) - compute_client.delete_server(server_obj, force=parsed_args.force) + compute_client.delete_server( + server_obj, force=parsed_args.force + ) + deleted_servers.append(server_obj) + except Exception as e: + LOG.error( + _( + "Failed to delete server with " + "name or ID '%(server)s': %(e)s" + ), + {'server': server, 'e': e}, + ) - if parsed_args.wait: + if parsed_args.wait: + for server_obj in deleted_servers: try: compute_client.wait_for_delete( server_obj, callback=_show_progress ) except sdk_exceptions.ResourceTimeout: msg = _('Error deleting server: %s') % server_obj.id + deleted_servers.remove(server_obj) raise exceptions.CommandError(msg) + fails = len(parsed_args.server) - len(deleted_servers) + if fails > 0: + total = len(parsed_args.server) + msg = _("%(fails)s of %(total)s servers failed to delete.") % { + 'fails': fails, + 'total': total, + } + raise exceptions.CommandError(msg) + class PercentAction(argparse.Action): def __init__( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 1d55d1efed..68e1d9ac2d 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4449,6 +4449,55 @@ class TestServerDelete(compute_fakes.TestComputev2): ) self.assertIsNone(result) + def test_server_delete_multi_servers_with_exceptions(self): + servers = compute_fakes.create_servers(count=2) + self.compute_client.find_server.side_effect = [ + servers[0], + sdk_exceptions.ResourceNotFound(), + servers[1], + ] + + arglist = [servers[0].id, 'unexist_server', servers[1].id] + + verifylist = [ + ('force', False), + ('all_projects', False), + ('wait', False), + ( + 'server', + [servers[0].id, 'unexist_server', servers[1].id], + ), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + self.assertEqual('1 of 3 servers failed to delete.', str(exc)) + + self.compute_client.find_server.assert_has_calls( + [ + mock.call( + servers[0].id, ignore_missing=False, all_projects=False + ), + mock.call( + 'unexist_server', ignore_missing=False, all_projects=False + ), + mock.call( + servers[1].id, ignore_missing=False, all_projects=False + ), + ] + ) + + self.compute_client.delete_server.assert_has_calls( + [ + mock.call(servers[0], force=False), + mock.call(servers[1], force=False), + ] + ) + def test_server_delete_with_all_projects(self): arglist = [ self.server.id,