Skip to content

Commit af7ab03

Browse files
Huanxuan Aostevemar
authored andcommitted
Support bulk deletion for delete commands in computev2
Support bulk deletion and error handling for "keypair delete" and "service delete" commands in computev2. Up to now, all the delete commands in computev2 support bulk deletion. Change-Id: I6d5c960e9716188e56615514d0921618a15a88ec Partially-Implements: blueprint multi-argument-compute Partial-Bug: #1592906
1 parent 044a46e commit af7ab03

8 files changed

Lines changed: 174 additions & 25 deletions

File tree

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ Compute v2
77
compute service delete
88
----------------------
99

10-
Delete service command
10+
Delete compute service(s)
1111

1212
.. program:: compute service delete
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: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,35 @@
2929

3030

3131
class DeleteService(command.Command):
32-
"""Delete service command"""
32+
"""Delete compute service(s)"""
3333

3434
def get_parser(self, prog_name):
3535
parser = super(DeleteService, self).get_parser(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)