From 3b8f24ea81caeac429c2c1cf4fd048cf741cab66 Mon Sep 17 00:00:00 2001
From: Valeriy Ponomaryov <vponomaryov@mirantis.com>
Date: Mon, 3 Oct 2016 19:28:22 +0300
Subject: [PATCH] Fix share-server-delete command

and add functional tests for it.
Which can be enabled/disabled using
'run_share_servers_tests' config option.

Change-Id: I07c5b03d576b41a61867a90d828a112c8a3f4f4b
Closes-Bug: #1629317
---
 manilaclient/config.py                        |   6 +
 manilaclient/tests/functional/client.py       |  71 ++++++-
 .../tests/functional/test_share_servers.py    | 123 ++++++++++--
 manilaclient/tests/unit/v2/fakes.py           |  16 +-
 manilaclient/tests/unit/v2/test_shell.py      | 183 +++++++++++-------
 manilaclient/v2/share_servers.py              |  19 +-
 manilaclient/v2/shell.py                      |  14 +-
 7 files changed, 329 insertions(+), 103 deletions(-)

diff --git a/manilaclient/config.py b/manilaclient/config.py
index 419906bff..fa753ca20 100644
--- a/manilaclient/config.py
+++ b/manilaclient/config.py
@@ -130,6 +130,12 @@ share_opts = [
                 help="Defines whether to run tests that use share snapshots "
                      "or not. Disable this feature if used driver doesn't "
                      "support it."),
+    cfg.BoolOpt("run_share_servers_tests",
+                default=True,
+                help="Defines whether to run tests that use share servers "
+                     "or not. Disable this feature if used driver doesn't "
+                     "support it or when autodeletion of share servers "
+                     "is enabled."),
 ]
 
 # 2. Generate config
diff --git a/manilaclient/tests/functional/client.py b/manilaclient/tests/functional/client.py
index 5d5d50e27..e90240fa4 100644
--- a/manilaclient/tests/functional/client.py
+++ b/manilaclient/tests/functional/client.py
@@ -31,6 +31,7 @@ CONF = config.CONF
 SHARE = 'share'
 SHARE_TYPE = 'share_type'
 SHARE_NETWORK = 'share_network'
+SHARE_SERVER = 'share_server'
 
 
 def not_found_wrapper(f):
@@ -39,9 +40,10 @@ def not_found_wrapper(f):
         try:
             return f(self, *args, **kwargs)
         except tempest_lib_exc.CommandFailed as e:
-            if re.search('No (\w+) with a name or ID', e.stderr):
-                # Raise appropriate 'NotFound' error
-                raise tempest_lib_exc.NotFound()
+            for regexp in ('No (\w+) with a name or ID', 'not found'):
+                if re.search(regexp, e.stderr):
+                    # Raise appropriate 'NotFound' error
+                    raise tempest_lib_exc.NotFound()
             raise
 
     return wrapped_func
@@ -123,6 +125,8 @@ class ManilaCLIClient(base.CLIClient):
             func = self.is_share_type_deleted
         elif res_type == SHARE_NETWORK:
             func = self.is_share_network_deleted
+        elif res_type == SHARE_SERVER:
+            func = self.is_share_server_deleted
         elif res_type == SHARE:
             func = self.is_share_deleted
         elif res_type == SNAPSHOT:
@@ -1068,3 +1072,64 @@ class ManilaCLIClient(base.CLIClient):
             microversion=microversion)
         share = output_parser.details(share_raw)
         return share
+
+    # Share servers
+
+    @not_found_wrapper
+    def get_share_server(self, share_server, microversion=None):
+        """Returns share server by its Name or ID."""
+        share_server_raw = self.manila(
+            'share-server-show %s' % share_server, microversion=microversion)
+        share_server = output_parser.details(share_server_raw)
+        return share_server
+
+    def list_share_servers(self, filters=None, columns=None,
+                           microversion=None):
+        """List share servers.
+
+        :param filters: dict -- filters for listing of share servers.
+            Example, input:
+                {'project_id': 'foo'}
+                {'-project_id': 'foo'}
+                {'--project_id': 'foo'}
+                {'project-id': 'foo'}
+            will be transformed to filter parameter "--project-id=foo"
+         :param columns: comma separated string of columns.
+            Example, "--columns id"
+        """
+        cmd = 'share-server-list '
+        if columns is not None:
+            cmd += ' --columns ' + columns
+        if filters and isinstance(filters, dict):
+            for k, v in filters.items():
+                cmd += '%(k)s=%(v)s ' % {
+                    'k': self._stranslate_to_cli_optional_param(k), 'v': v}
+        share_servers_raw = self.manila(cmd, microversion=microversion)
+        share_servers = utils.listing(share_servers_raw)
+        return share_servers
+
+    @not_found_wrapper
+    def delete_share_server(self, share_server, microversion=None):
+        """Deletes share server by its Name or ID."""
+        return self.manila('share-server-delete %s' % share_server,
+                           microversion=microversion)
+
+    def is_share_server_deleted(self, share_server_id, microversion=None):
+        """Says whether share server is deleted or not.
+
+        :param share_server: text -- ID of the share server
+        """
+        servers = self.list_share_servers(microversion=microversion)
+        for list_element in servers:
+            if share_server_id == list_element['Id']:
+                return False
+        return True
+
+    def wait_for_share_server_deletion(self, share_server, microversion=None):
+        """Wait for share server deletion by its Name or ID.
+
+        :param share_server: text -- Name or ID of share server
+        """
+        self.wait_for_resource_deletion(
+            SHARE_SERVER, res_id=share_server, interval=3, timeout=60,
+            microversion=microversion)
diff --git a/manilaclient/tests/functional/test_share_servers.py b/manilaclient/tests/functional/test_share_servers.py
index b648df935..f7b9446f1 100644
--- a/manilaclient/tests/functional/test_share_servers.py
+++ b/manilaclient/tests/functional/test_share_servers.py
@@ -14,34 +14,133 @@
 #    under the License.
 
 import ddt
+from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions
 
+from manilaclient import config
 from manilaclient.tests.functional import base
 
+CONF = config.CONF
+
 
 @ddt.ddt
-class ManilaClientTestShareServersReadOnly(base.BaseTestCase):
+class ShareServersReadOnlyTest(base.BaseTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(ShareServersReadOnlyTest, cls).setUpClass()
+        cls.client = cls.get_admin_client()
 
     def test_share_server_list(self):
-        self.clients['admin'].manila('share-server-list')
+        self.client.list_share_servers()
 
     def test_share_server_list_with_host_param(self):
-        self.clients['admin'].manila('share-server-list', params='--host host')
+        self.client.list_share_servers(filters={'host': 'fake_host'})
 
     def test_share_server_list_with_status_param(self):
-        self.clients['admin'].manila(
-            'share-server-list', params='--status status')
+        self.client.list_share_servers(filters={'status': 'fake_status'})
 
     def test_share_server_list_with_share_network_param(self):
-        self.clients['admin'].manila(
-            'share-server-list', params='--share-network share-network')
+        self.client.list_share_servers(filters={'share_network': 'fake_sn'})
 
     def test_share_server_list_with_project_id_param(self):
-        self.clients['admin'].manila(
-            'share-server-list', params='--project-id project-id')
+        self.client.list_share_servers(
+            filters={'project_id': 'fake_project_id'})
+
+    @ddt.data(
+        'host', 'status', 'project_id', 'share_network',
+        'host,status,project_id,share_network',
+    )
+    def test_share_server_list_with_specified_columns(self, columns):
+        self.client.list_share_servers(columns=columns)
 
     def test_share_server_list_by_user(self):
         self.assertRaises(
-            exceptions.CommandFailed,
-            self.clients['user'].manila,
-            'share-server-list')
+            exceptions.CommandFailed, self.user_client.list_share_servers)
+
+
+@ddt.ddt
+class ShareServersReadWriteBase(base.BaseTestCase):
+
+    protocol = None
+
+    @classmethod
+    def setUpClass(cls):
+        super(ShareServersReadWriteBase, cls).setUpClass()
+        if not CONF.run_share_servers_tests:
+            message = "share-servers tests are disabled."
+            raise cls.skipException(message)
+        if cls.protocol not in CONF.enable_protocols:
+            message = "%s tests are disabled." % cls.protocol
+            raise cls.skipException(message)
+
+        cls.client = cls.get_admin_client()
+        if not cls.client.share_network:
+            message = "Can run only with DHSS=True mode"
+            raise cls.skipException(message)
+
+    def test_get_and_delete_share_server(self):
+        name = data_utils.rand_name('autotest_share_name')
+        description = data_utils.rand_name('autotest_share_description')
+
+        # We create separate share network to be able to delete share server
+        # further knowing that it is not used by any other concurrent test.
+        common_share_network = self.client.get_share_network(
+            self.client.share_network)
+        neutron_net_id = (
+            common_share_network['neutron_net_id']
+            if 'none' not in common_share_network['neutron_net_id'].lower()
+            else None)
+        neutron_subnet_id = (
+            common_share_network['neutron_subnet_id']
+            if 'none' not in common_share_network['neutron_subnet_id'].lower()
+            else None)
+        share_network = self.client.create_share_network(
+            neutron_net_id=neutron_net_id,
+            neutron_subnet_id=neutron_subnet_id,
+        )
+
+        self.share = self.create_share(
+            share_protocol=self.protocol,
+            size=1,
+            name=name,
+            description=description,
+            share_network=share_network['id'],
+            client=self.client,
+        )
+        share_server_id = self.client.get_share(
+            self.share['id'])['share_server_id']
+
+        # Get share server
+        server = self.client.get_share_server(share_server_id)
+        expected_keys = (
+            'id', 'host', 'status', 'created_at', 'updated_at',
+            'share_network_id', 'share_network_name', 'project_id',
+        )
+        for key in expected_keys:
+            self.assertIn(key, server)
+
+        # Delete share
+        self.client.delete_share(self.share['id'])
+        self.client.wait_for_share_deletion(self.share['id'])
+
+        # Delete share server
+        self.client.delete_share_server(share_server_id)
+        self.client.wait_for_share_server_deletion(share_server_id)
+
+
+class ShareServersReadWriteNFSTest(ShareServersReadWriteBase):
+    protocol = 'nfs'
+
+
+class ShareServersReadWriteCIFSTest(ShareServersReadWriteBase):
+    protocol = 'cifs'
+
+
+def load_tests(loader, tests, _):
+    result = []
+    for test_case in tests:
+        if type(test_case._tests[0]) is ShareServersReadWriteBase:
+            continue
+        result.append(test_case)
+    return loader.suiteClass(result)
diff --git a/manilaclient/tests/unit/v2/fakes.py b/manilaclient/tests/unit/v2/fakes.py
index 44caca06a..cf92467d1 100644
--- a/manilaclient/tests/unit/v2/fakes.py
+++ b/manilaclient/tests/unit/v2/fakes.py
@@ -169,7 +169,16 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
         share_servers = {
             'share_servers': {
                 'id': 1234,
-                'share_network_id': 'fake_network_id',
+                'share_network_id': 'fake_network_id_1',
+            },
+        }
+        return (200, {}, share_servers)
+
+    def get_share_servers_5678(self, **kw):
+        share_servers = {
+            'share_servers': {
+                'id': 5678,
+                'share_network_id': 'fake_network_id_2',
             },
         }
         return (200, {}, share_servers)
@@ -254,6 +263,8 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
             raise AssertionError("Unexpected action: %s" % action)
         return (resp, {}, _body)
 
+    post_snapshots_5678_action = post_snapshots_1234_action
+
     def post_snapshots_manage(self, body, **kw):
         _body = {'snapshot': {'id': 'fake'}}
         resp = 202
@@ -452,6 +463,9 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
     def delete_share_servers_1234(self, **kwargs):
         return (202, {}, None)
 
+    def delete_share_servers_5678(self, **kwargs):
+        return (202, {}, None)
+
     def delete_security_services_fake_security_service1(self, **kwargs):
         return (202, {}, None)
 
diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py
index cf7723633..510e5100b 100644
--- a/manilaclient/tests/unit/v2/test_shell.py
+++ b/manilaclient/tests/unit/v2/test_shell.py
@@ -29,7 +29,12 @@ from manilaclient import exceptions
 from manilaclient import shell
 from manilaclient.tests.unit import utils as test_utils
 from manilaclient.tests.unit.v2 import fakes
+from manilaclient.v2 import security_services
 from manilaclient.v2 import share_instances
+from manilaclient.v2 import share_networks
+from manilaclient.v2 import share_servers
+from manilaclient.v2 import share_snapshots
+from manilaclient.v2 import share_types
 from manilaclient.v2 import shell as shell_v2
 
 
@@ -1974,93 +1979,125 @@ class ShellTest(test_utils.TestCase):
         expected = {'reset_task_state': {'task_state': param}}
         self.assert_called('POST', '/shares/1234/action', body=expected)
 
-    @ddt.data('fake-security-service1',
-              'fake-security-service1 fake-security-service2')
-    @mock.patch.object(shell_v2, '_find_security_service', mock.Mock())
-    def test_security_service_delete(self, args):
-        args_split = args.split()
-        shell_v2._find_security_service.side_effect = args_split
+    @ddt.data(('fake_security_service1', ),
+              ('fake_security_service1', 'fake_security_service2'))
+    def test_security_service_delete(self, ss_ids):
+        fake_security_services = [
+            security_services.SecurityService('fake', {'id': ss_id}, True)
+            for ss_id in ss_ids
+        ]
+        self.mock_object(
+            shell_v2, '_find_security_service',
+            mock.Mock(side_effect=fake_security_services))
 
-        self.run_command('security-service-delete %s' % args)
+        self.run_command('security-service-delete %s' % ' '.join(ss_ids))
 
-        self.assert_called_anytime(
-            'DELETE', '/security-services/%s' % args_split[0],
-            clear_callstack=False)
-        self.assert_called_anytime(
-            'DELETE', '/security-services/%s' % args_split[-1])
+        shell_v2._find_security_service.assert_has_calls([
+            mock.call(self.shell.cs, ss_id) for ss_id in ss_ids
+        ])
+        for ss in fake_security_services:
+            self.assert_called_anytime(
+                'DELETE', '/security-services/%s' % ss.id,
+                clear_callstack=False)
 
-    @ddt.data('fake-share-network1',
-              'fake-share-network1 fake-share-network2')
-    @mock.patch.object(shell_v2, '_find_share_network', mock.Mock())
-    def test_share_network_delete(self, args):
-        args_split = args.split()
-        shell_v2._find_share_network.side_effect = args_split
+    @ddt.data(('fake_share_network1', ),
+              ('fake_share_network1', 'fake_share_network1'))
+    def test_share_network_delete(self, sn_ids):
+        fake_share_networks = [
+            share_networks.ShareNetwork('fake', {'id': sn_id}, True)
+            for sn_id in sn_ids
+        ]
+        self.mock_object(
+            shell_v2, '_find_share_network',
+            mock.Mock(side_effect=fake_share_networks))
 
-        self.run_command('share-network-delete %s' % args)
+        self.run_command('share-network-delete %s' % ' '.join(sn_ids))
 
-        self.assert_called_anytime(
-            'DELETE', '/share-networks/%s' % args_split[0],
-            clear_callstack=False)
-        self.assert_called_anytime(
-            'DELETE', '/share-networks/%s' % args_split[-1])
+        shell_v2._find_share_network.assert_has_calls([
+            mock.call(self.shell.cs, sn_id) for sn_id in sn_ids
+        ])
+        for sn in fake_share_networks:
+            self.assert_called_anytime(
+                'DELETE', '/share-networks/%s' % sn.id,
+                clear_callstack=False)
 
-    @ddt.data('fake-snapshot1',
-              'fake-snapshot1 fake-snapshot2')
-    @mock.patch.object(shell_v2, '_find_share_snapshot', mock.Mock())
-    def test_snapshot_delete(self, args):
-        args_split = args.split()
-        shell_v2._find_share_snapshot.side_effect = args_split
+    @ddt.data(('fake_snapshot1', ), ('fake_snapshot1', 'fake_snapshot2'))
+    def test_snapshot_delete(self, snapshot_ids):
+        fake_snapshots = [
+            share_snapshots.ShareSnapshot('fake', {'id': snapshot_id}, True)
+            for snapshot_id in snapshot_ids
+        ]
+        self.mock_object(
+            shell_v2, '_find_share_snapshot',
+            mock.Mock(side_effect=fake_snapshots))
 
-        self.run_command('snapshot-delete %s' % args)
+        self.run_command('snapshot-delete %s' % ' '.join(snapshot_ids))
 
-        self.assert_called_anytime(
-            'DELETE', '/snapshots/%s' % args_split[0],
-            clear_callstack=False)
-        self.assert_called_anytime(
-            'DELETE', '/snapshots/%s' % args_split[-1])
+        shell_v2._find_share_snapshot.assert_has_calls([
+            mock.call(self.shell.cs, s_id) for s_id in snapshot_ids
+        ])
+        for snapshot in fake_snapshots:
+            self.assert_called_anytime(
+                'DELETE', '/snapshots/%s' % snapshot.id,
+                clear_callstack=False)
 
-    @ddt.data('fake-snapshot-force1',
-              'fake-snapshot-force1 fake-snapshot-force2')
-    @mock.patch.object(shell_v2, '_find_share_snapshot', mock.Mock())
-    def test_snapshot_delete_force(self, args):
-        args_split = args.split()
-        shell_v2._find_share_snapshot.side_effect = args_split
+    @ddt.data(('1234', ), ('1234', '5678'))
+    def test_snapshot_force_delete(self, snapshot_ids):
+        fake_snapshots = [
+            share_snapshots.ShareSnapshot('fake', {'id': snapshot_id}, True)
+            for snapshot_id in snapshot_ids
+        ]
+        self.mock_object(
+            shell_v2, '_find_share_snapshot',
+            mock.Mock(side_effect=fake_snapshots))
 
-        self.run_command('snapshot-force-delete %s' % args)
+        self.run_command('snapshot-force-delete %s' % ' '.join(snapshot_ids))
 
-        self.assert_called_anytime(
-            'POST', '/snapshots/%s/action' % args_split[0],
-            {'force_delete': None}, clear_callstack=False)
-        self.assert_called_anytime(
-            'POST', '/snapshots/%s/action' % args_split[-1],
-            {'force_delete': None})
+        shell_v2._find_share_snapshot.assert_has_calls([
+            mock.call(self.shell.cs, s_id) for s_id in snapshot_ids
+        ])
+        for snapshot in fake_snapshots:
+            self.assert_called_anytime(
+                'POST', '/snapshots/%s/action' % snapshot.id,
+                {'force_delete': None},
+                clear_callstack=False)
 
-    @ddt.data('fake-type1',
-              'fake-type1 fake-type2')
-    @mock.patch.object(shell_v2, '_find_share_type', mock.Mock())
-    def test_share_type_delete(self, args):
-        args_split = args.split()
-        shell_v2._find_share_type.side_effect = args_split
+    @ddt.data(('fake_type1', ), ('fake_type1', 'fake_type2'))
+    def test_share_type_delete(self, type_ids):
+        fake_share_types = [
+            share_types.ShareType('fake', {'id': type_id}, True)
+            for type_id in type_ids
+        ]
+        self.mock_object(
+            shell_v2, '_find_share_type',
+            mock.Mock(side_effect=fake_share_types))
 
-        self.run_command('type-delete %s' % args)
+        self.run_command('type-delete %s' % ' '.join(type_ids))
 
-        self.assert_called_anytime(
-            'DELETE', '/types/%s' % args_split[0],
-            clear_callstack=False)
-        self.assert_called_anytime(
-            'DELETE', '/types/%s' % args_split[-1])
+        shell_v2._find_share_type.assert_has_calls([
+            mock.call(self.shell.cs, t_id) for t_id in type_ids
+        ])
+        for fake_share_type in fake_share_types:
+            self.assert_called_anytime(
+                'DELETE', '/types/%s' % fake_share_type.id,
+                clear_callstack=False)
 
-    @ddt.data('fake-share-server1',
-              'fake-share-server1 fake-share-server2')
-    @mock.patch.object(shell_v2, '_find_share_server', mock.Mock())
-    def test_share_server_delete(self, args):
-        args_split = args.split()
-        shell_v2._find_share_server.side_effect = args_split
+    @ddt.data(('1234', ), ('1234', '5678'))
+    def test_share_server_delete(self, server_ids):
+        fake_share_servers = [
+            share_servers.ShareServer('fake', {'id': server_id}, True)
+            for server_id in server_ids
+        ]
+        self.mock_object(
+            shell_v2, '_find_share_server',
+            mock.Mock(side_effect=fake_share_servers))
 
-        self.run_command('share-server-delete %s' % args)
+        self.run_command('share-server-delete %s' % ' '.join(server_ids))
 
-        self.assert_called_anytime(
-            'DELETE', '/share-servers/%s' % args_split[0],
-            clear_callstack=False)
-        self.assert_called_anytime(
-            'DELETE', '/share-servers/%s' % args_split[-1])
+        shell_v2._find_share_server.assert_has_calls([
+            mock.call(self.shell.cs, s_id) for s_id in server_ids
+        ])
+        for server in fake_share_servers:
+            self.assert_called_anytime(
+                'DELETE', '/share-servers/%s' % server.id,
+                clear_callstack=False)
diff --git a/manilaclient/v2/share_servers.py b/manilaclient/v2/share_servers.py
index c820e5ab1..d13f3d8f8 100644
--- a/manilaclient/v2/share_servers.py
+++ b/manilaclient/v2/share_servers.py
@@ -38,17 +38,22 @@ class ShareServer(common_base.Resource):
             attr = 'share_network_name'
         return super(ShareServer, self).__getattr__(attr)
 
+    def delete(self):
+        """Delete this share server."""
+        self.manager.delete(self)
+
 
 class ShareServerManager(base.ManagerWithFind):
     """Manage :class:`ShareServer` resources."""
     resource_class = ShareServer
 
-    def get(self, server_id):
+    def get(self, server):
         """Get a share server.
 
-        :param server_id: The ID of the share server to get.
+        :param server: ID of the :class:`ShareServer` to get.
         :rtype: :class:`ShareServer`
         """
+        server_id = common_base.getid(server)
         server = self._get("%s/%s" % (RESOURCES_PATH, server_id),
                            RESOURCE_NAME)
         # Split big dict 'backend_details' to separated strings
@@ -62,20 +67,22 @@ class ShareServerManager(base.ManagerWithFind):
             server._info["details:%s" % k] = v
         return server
 
-    def details(self, server_id):
+    def details(self, server):
         """Get a share server details.
 
-        :param server_id: The ID of the share server to get details from.
+        :param server: ID of the :class:`ShareServer` to get details from.
         :rtype: list of :class:`ShareServerBackendDetails
         """
+        server_id = common_base.getid(server)
         return self._get("%s/%s/details" % (RESOURCES_PATH, server_id),
                          "details")
 
-    def delete(self, server_id):
+    def delete(self, server):
         """Delete share server.
 
-        :param server_id: id of share server to be deleted.
+        :param server: ID of the :class:`ShareServer` to delete.
         """
+        server_id = common_base.getid(server)
         self._delete(RESOURCE_PATH % server_id)
 
     def list(self, search_opts=None):
diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py
index 37d2c7773..02fb1eb07 100644
--- a/manilaclient/v2/shell.py
+++ b/manilaclient/v2/shell.py
@@ -2605,15 +2605,14 @@ def do_share_server_delete(cs, args):
 
     failure_count = 0
 
-    for id in args.id:
+    for server_id in args.id:
         try:
-            id_ref = _find_share_server(
-                cs, id)
+            id_ref = _find_share_server(cs, server_id)
             cs.share_servers.delete(id_ref)
         except Exception as e:
             failure_count += 1
             print("Delete for share server %s failed: %s" % (
-                id, e), file=sys.stderr)
+                server_id, e), file=sys.stderr)
 
     if failure_count == len(args.id):
         raise exceptions.CommandError("Unable to delete any of the specified "
@@ -2950,15 +2949,14 @@ def do_type_delete(cs, args):
 
     failure_count = 0
 
-    for id in args.id:
+    for name_or_id in args.id:
         try:
-            id_ref = _find_share_type(
-                cs, id)
+            id_ref = _find_share_type(cs, name_or_id)
             cs.share_types.delete(id_ref)
         except Exception as e:
             failure_count += 1
             print("Delete for share type %s failed: %s" % (
-                id, e), file=sys.stderr)
+                name_or_id, e), file=sys.stderr)
 
     if failure_count == len(args.id):
         raise exceptions.CommandError("Unable to delete any of the specified "