Skip to content

Commit 21ad61d

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Support bulk deletion for delete commands in computev2"
2 parents 1ce5471 + af7ab03 commit 21ad61d

8 files changed

Lines changed: 172 additions & 23 deletions

File tree

doc/source/command-objects/compute-service.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ Delete compute service(s)
1313
.. code:: bash
1414
1515
os compute service delete
16-
<service>
16+
<service> [<service> ...]
1717
1818
.. _compute-service-delete:
1919
.. describe:: <service>
2020

21-
Compute service to delete (ID only)
21+
Compute service(s) to delete (ID only)
2222

2323
compute service list
2424
--------------------

doc/source/command-objects/keypair.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ Create new public key
3030
keypair delete
3131
--------------
3232

33-
Delete public key
33+
Delete public key(s)
3434

3535
.. program:: keypair delete
3636
.. code:: bash
3737
3838
os keypair delete
39-
<key>
39+
<key> [<key> ...]
4040
4141
.. describe:: <key>
4242

43-
Public key to delete (name only)
43+
Public key(s) to delete (name only)
4444

4545
keypair list
4646
------------

openstackclient/compute/v2/keypair.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"""Keypair action implementations"""
1717

1818
import io
19+
import logging
1920
import os
2021
import sys
2122

@@ -27,6 +28,9 @@
2728
from openstackclient.i18n import _
2829

2930

31+
LOG = logging.getLogger(__name__)
32+
33+
3034
class CreateKeypair(command.ShowOne):
3135
"""Create new public key"""
3236

@@ -78,20 +82,37 @@ def take_action(self, parsed_args):
7882

7983

8084
class DeleteKeypair(command.Command):
81-
"""Delete public key"""
85+
"""Delete public key(s)"""
8286

8387
def get_parser(self, prog_name):
8488
parser = super(DeleteKeypair, self).get_parser(prog_name)
8589
parser.add_argument(
8690
'name',
8791
metavar='<key>',
88-
help=_("Public key to delete (name only)")
92+
nargs='+',
93+
help=_("Public key(s) to delete (name only)")
8994
)
9095
return parser
9196

9297
def take_action(self, parsed_args):
9398
compute_client = self.app.client_manager.compute
94-
compute_client.keypairs.delete(parsed_args.name)
99+
result = 0
100+
for n in parsed_args.name:
101+
try:
102+
data = utils.find_resource(
103+
compute_client.keypairs, n)
104+
compute_client.keypairs.delete(data.name)
105+
except Exception as e:
106+
result += 1
107+
LOG.error(_("Failed to delete public key with name "
108+
"'%(name)s': %(e)s")
109+
% {'name': n, 'e': e})
110+
111+
if result > 0:
112+
total = len(parsed_args.name)
113+
msg = (_("%(result)s of %(total)s public keys failed "
114+
"to delete.") % {'result': result, 'total': total})
115+
raise exceptions.CommandError(msg)
95116

96117

97118
class ListKeypair(command.Lister):

openstackclient/compute/v2/service.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,28 @@ def get_parser(self, prog_name):
3636
parser.add_argument(
3737
"service",
3838
metavar="<service>",
39-
help=_("Compute service to delete (ID only)")
39+
nargs='+',
40+
help=_("Compute service(s) to delete (ID only)")
4041
)
4142
return parser
4243

4344
def take_action(self, parsed_args):
4445
compute_client = self.app.client_manager.compute
46+
result = 0
47+
for s in parsed_args.service:
48+
try:
49+
compute_client.services.delete(s)
50+
except Exception as e:
51+
result += 1
52+
LOG.error(_("Failed to delete compute service with "
53+
"ID '%(service)s': %(e)s")
54+
% {'service': s, 'e': e})
4555

46-
compute_client.services.delete(parsed_args.service)
56+
if result > 0:
57+
total = len(parsed_args.service)
58+
msg = (_("%(result)s of %(total)s compute services failed "
59+
"to delete.") % {'result': result, 'total': total})
60+
raise exceptions.CommandError(msg)
4761

4862

4963
class ListService(command.Lister):

openstackclient/tests/compute/v2/fakes.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,25 @@ def create_keypairs(attrs=None, count=2):
826826

827827
return keypairs
828828

829+
@staticmethod
830+
def get_keypairs(keypairs=None, count=2):
831+
"""Get an iterable MagicMock object with a list of faked keypairs.
832+
833+
If keypairs list is provided, then initialize the Mock object with the
834+
list. Otherwise create one.
835+
836+
:param List keypairs:
837+
A list of FakeResource objects faking keypairs
838+
:param int count:
839+
The number of keypairs to fake
840+
:return:
841+
An iterable Mock object with side_effect set to a list of faked
842+
keypairs
843+
"""
844+
if keypairs is None:
845+
keypairs = FakeKeypair.create_keypairs(count)
846+
return mock.MagicMock(side_effect=keypairs)
847+
829848

830849
class FakeAvailabilityZone(object):
831850
"""Fake one or more compute availability zones (AZs)."""

openstackclient/tests/compute/v2/test_keypair.py

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#
1515

1616
import mock
17+
from mock import call
18+
19+
from osc_lib import exceptions
20+
from osc_lib import utils
1721

1822
from openstackclient.compute.v2 import keypair
1923
from openstackclient.tests.compute.v2 import fakes as compute_fakes
@@ -114,30 +118,78 @@ def test_keypair_create_public_key(self):
114118

115119
class TestKeypairDelete(TestKeypair):
116120

117-
keypair = compute_fakes.FakeKeypair.create_one_keypair()
121+
keypairs = compute_fakes.FakeKeypair.create_keypairs(count=2)
118122

119123
def setUp(self):
120124
super(TestKeypairDelete, self).setUp()
121125

122-
self.keypairs_mock.get.return_value = self.keypair
126+
self.keypairs_mock.get = compute_fakes.FakeKeypair.get_keypairs(
127+
self.keypairs)
123128
self.keypairs_mock.delete.return_value = None
124129

125130
self.cmd = keypair.DeleteKeypair(self.app, None)
126131

127132
def test_keypair_delete(self):
128133
arglist = [
129-
self.keypair.name
134+
self.keypairs[0].name
130135
]
131136
verifylist = [
132-
('name', self.keypair.name),
137+
('name', [self.keypairs[0].name]),
133138
]
134139

135140
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
136141

137142
ret = self.cmd.take_action(parsed_args)
138143

139144
self.assertIsNone(ret)
140-
self.keypairs_mock.delete.assert_called_with(self.keypair.name)
145+
self.keypairs_mock.delete.assert_called_with(self.keypairs[0].name)
146+
147+
def test_delete_multiple_keypairs(self):
148+
arglist = []
149+
for k in self.keypairs:
150+
arglist.append(k.name)
151+
verifylist = [
152+
('name', arglist),
153+
]
154+
155+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
156+
result = self.cmd.take_action(parsed_args)
157+
158+
calls = []
159+
for k in self.keypairs:
160+
calls.append(call(k.name))
161+
self.keypairs_mock.delete.assert_has_calls(calls)
162+
self.assertIsNone(result)
163+
164+
def test_delete_multiple_keypairs_with_exception(self):
165+
arglist = [
166+
self.keypairs[0].name,
167+
'unexist_keypair',
168+
]
169+
verifylist = [
170+
('name', arglist),
171+
]
172+
173+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
174+
175+
find_mock_result = [self.keypairs[0], exceptions.CommandError]
176+
with mock.patch.object(utils, 'find_resource',
177+
side_effect=find_mock_result) as find_mock:
178+
try:
179+
self.cmd.take_action(parsed_args)
180+
self.fail('CommandError should be raised.')
181+
except exceptions.CommandError as e:
182+
self.assertEqual('1 of 2 public keys failed to delete.',
183+
str(e))
184+
185+
find_mock.assert_any_call(
186+
self.keypairs_mock, self.keypairs[0].name)
187+
find_mock.assert_any_call(self.keypairs_mock, 'unexist_keypair')
188+
189+
self.assertEqual(2, find_mock.call_count)
190+
self.keypairs_mock.delete.assert_called_once_with(
191+
self.keypairs[0].name
192+
)
141193

142194

143195
class TestKeypairList(TestKeypair):

openstackclient/tests/compute/v2/test_service.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#
1515

1616
import mock
17+
from mock import call
1718

1819
from osc_lib import exceptions
1920

@@ -33,32 +34,74 @@ def setUp(self):
3334

3435
class TestServiceDelete(TestService):
3536

37+
services = compute_fakes.FakeService.create_services(count=2)
38+
3639
def setUp(self):
3740
super(TestServiceDelete, self).setUp()
3841

39-
self.service = compute_fakes.FakeService.create_one_service()
40-
4142
self.service_mock.delete.return_value = None
4243

4344
# Get the command object to test
4445
self.cmd = service.DeleteService(self.app, None)
4546

46-
def test_service_delete_no_options(self):
47+
def test_service_delete(self):
4748
arglist = [
48-
self.service.binary,
49+
self.services[0].binary,
4950
]
5051
verifylist = [
51-
('service', self.service.binary),
52+
('service', [self.services[0].binary]),
5253
]
5354
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
5455

5556
result = self.cmd.take_action(parsed_args)
5657

5758
self.service_mock.delete.assert_called_with(
58-
self.service.binary,
59+
self.services[0].binary,
5960
)
6061
self.assertIsNone(result)
6162

63+
def test_multi_services_delete(self):
64+
arglist = []
65+
for s in self.services:
66+
arglist.append(s.binary)
67+
verifylist = [
68+
('service', arglist),
69+
]
70+
71+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
72+
result = self.cmd.take_action(parsed_args)
73+
74+
calls = []
75+
for s in self.services:
76+
calls.append(call(s.binary))
77+
self.service_mock.delete.assert_has_calls(calls)
78+
self.assertIsNone(result)
79+
80+
def test_multi_services_delete_with_exception(self):
81+
arglist = [
82+
self.services[0].binary,
83+
'unexist_service',
84+
]
85+
verifylist = [
86+
('service', arglist)
87+
]
88+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
89+
90+
delete_mock_result = [None, exceptions.CommandError]
91+
self.service_mock.delete = (
92+
mock.MagicMock(side_effect=delete_mock_result)
93+
)
94+
95+
try:
96+
self.cmd.take_action(parsed_args)
97+
self.fail('CommandError should be raised.')
98+
except exceptions.CommandError as e:
99+
self.assertEqual(
100+
'1 of 2 compute services failed to delete.', str(e))
101+
102+
self.service_mock.delete.assert_any_call(self.services[0].binary)
103+
self.service_mock.delete.assert_any_call('unexist_service')
104+
62105

63106
class TestServiceList(TestService):
64107

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
22
features:
3-
- Support bulk deletion and error handling for ``aggregate delete`` and
4-
``flavor delete`` commands.
3+
- Support bulk deletion and error handling for ``aggregate delete``,
4+
``flavor delete``, ``keypair delete`` and ``service delete`` commands.
55
[Blueprint `multi-argument-compute <https://blueprints.launchpad.net/python-openstackclient/+spec/multi-argument-compute>`_]

0 commit comments

Comments
 (0)