Skip to content

Commit 640014f

Browse files
author
Huanxuan Ao
committed
Support bulk deletion for "flavor/aggregate delete"
Support bulk deletion and error handling for "aggregate delete" and "flavor delete" commands. Change-Id: I3f6105cbeeab1c9f8cd571c63ce0e7ac3d4252b3 Partially-Implements: blueprint multi-argument-compute Partial-Bug: #1592906
1 parent ba825a4 commit 640014f

8 files changed

Lines changed: 201 additions & 39 deletions

File tree

doc/source/command-objects/aggregate.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,17 @@ Create a new aggregate
5656
aggregate delete
5757
----------------
5858

59-
Delete an existing aggregate
59+
Delete existing aggregate(s)
6060

6161
.. program:: aggregate delete
6262
.. code:: bash
6363
6464
os aggregate delete
65-
<aggregate>
65+
<aggregate> [<aggregate> ...]
6666
6767
.. describe:: <aggregate>
6868

69-
Aggregate to delete (name or ID)
69+
Aggregate(s) to delete (name or ID)
7070

7171
aggregate list
7272
--------------

doc/source/command-objects/flavor.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,18 +67,18 @@ Create new flavor
6767
flavor delete
6868
-------------
6969

70-
Delete flavor
70+
Delete flavor(s)
7171

7272
.. program:: flavor delete
7373
.. code:: bash
7474
7575
os flavor delete
76-
<flavor>
76+
<flavor> [<flavor> ...]
7777
7878
.. _flavor_delete-flavor:
7979
.. describe:: <flavor>
8080

81-
Flavor to delete (name or ID)
81+
Flavor(s) to delete (name or ID)
8282

8383
flavor list
8484
-----------

openstackclient/compute/v2/aggregate.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,20 @@
1616

1717
"""Compute v2 Aggregate action implementations"""
1818

19+
import logging
20+
1921
from osc_lib.cli import parseractions
2022
from osc_lib.command import command
23+
from osc_lib import exceptions
2124
from osc_lib import utils
2225
import six
2326

2427
from openstackclient.i18n import _
2528

2629

30+
LOG = logging.getLogger(__name__)
31+
32+
2733
class AddAggregateHost(command.ShowOne):
2834
"""Add host to aggregate"""
2935

@@ -99,25 +105,37 @@ def take_action(self, parsed_args):
99105

100106

101107
class DeleteAggregate(command.Command):
102-
"""Delete an existing aggregate"""
108+
"""Delete existing aggregate(s)"""
103109

104110
def get_parser(self, prog_name):
105111
parser = super(DeleteAggregate, self).get_parser(prog_name)
106112
parser.add_argument(
107113
'aggregate',
108114
metavar='<aggregate>',
109-
help=_("Aggregate to delete (name or ID)")
115+
nargs='+',
116+
help=_("Aggregate(s) to delete (name or ID)")
110117
)
111118
return parser
112119

113120
def take_action(self, parsed_args):
114-
115121
compute_client = self.app.client_manager.compute
116-
data = utils.find_resource(
117-
compute_client.aggregates,
118-
parsed_args.aggregate,
119-
)
120-
compute_client.aggregates.delete(data.id)
122+
result = 0
123+
for a in parsed_args.aggregate:
124+
try:
125+
data = utils.find_resource(
126+
compute_client.aggregates, a)
127+
compute_client.aggregates.delete(data.id)
128+
except Exception as e:
129+
result += 1
130+
LOG.error(_("Failed to delete aggregate with name or "
131+
"ID '%(aggregate)s': %(e)s")
132+
% {'aggregate': a, 'e': e})
133+
134+
if result > 0:
135+
total = len(parsed_args.aggregate)
136+
msg = (_("%(result)s of %(total)s aggregates failed "
137+
"to delete.") % {'result': result, 'total': total})
138+
raise exceptions.CommandError(msg)
121139

122140

123141
class ListAggregate(command.Lister):

openstackclient/compute/v2/flavor.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
"""Flavor action implementations"""
1717

18+
import logging
19+
1820
from osc_lib.cli import parseractions
1921
from osc_lib.command import command
2022
from osc_lib import exceptions
@@ -25,6 +27,9 @@
2527
from openstackclient.identity import common as identity_common
2628

2729

30+
LOG = logging.getLogger(__name__)
31+
32+
2833
def _find_flavor(compute_client, flavor):
2934
try:
3035
return compute_client.flavors.get(flavor)
@@ -140,21 +145,36 @@ def take_action(self, parsed_args):
140145

141146

142147
class DeleteFlavor(command.Command):
143-
"""Delete flavor"""
148+
"""Delete flavor(s)"""
144149

145150
def get_parser(self, prog_name):
146151
parser = super(DeleteFlavor, self).get_parser(prog_name)
147152
parser.add_argument(
148153
"flavor",
149154
metavar="<flavor>",
150-
help=_("Flavor to delete (name or ID)")
155+
nargs='+',
156+
help=_("Flavor(s) to delete (name or ID)")
151157
)
152158
return parser
153159

154160
def take_action(self, parsed_args):
155161
compute_client = self.app.client_manager.compute
156-
flavor = _find_flavor(compute_client, parsed_args.flavor)
157-
compute_client.flavors.delete(flavor.id)
162+
result = 0
163+
for f in parsed_args.flavor:
164+
try:
165+
flavor = _find_flavor(compute_client, f)
166+
compute_client.flavors.delete(flavor.id)
167+
except Exception as e:
168+
result += 1
169+
LOG.error(_("Failed to delete flavor with name or "
170+
"ID '%(flavor)s': %(e)s")
171+
% {'flavor': f, 'e': e})
172+
173+
if result > 0:
174+
total = len(parsed_args.flavor)
175+
msg = (_("%(result)s of %(total)s flavors failed "
176+
"to delete.") % {'result': result, 'total': total})
177+
raise exceptions.CommandError(msg)
158178

159179

160180
class ListFlavor(command.Lister):

openstackclient/tests/compute/v2/fakes.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,42 @@ def create_one_aggregate(attrs=None):
8787
loaded=True)
8888
return aggregate
8989

90+
@staticmethod
91+
def create_aggregates(attrs=None, count=2):
92+
"""Create multiple fake aggregates.
93+
94+
:param Dictionary attrs:
95+
A dictionary with all attributes
96+
:param int count:
97+
The number of aggregates to fake
98+
:return:
99+
A list of FakeResource objects faking the aggregates
100+
"""
101+
aggregates = []
102+
for i in range(0, count):
103+
aggregates.append(FakeAggregate.create_one_aggregate(attrs))
104+
105+
return aggregates
106+
107+
@staticmethod
108+
def get_aggregates(aggregates=None, count=2):
109+
"""Get an iterable MagicMock object with a list of faked aggregates.
110+
111+
If aggregates list is provided, then initialize the Mock object
112+
with the list. Otherwise create one.
113+
114+
:param List aggregates:
115+
A list of FakeResource objects faking aggregates
116+
:param int count:
117+
The number of aggregates to fake
118+
:return:
119+
An iterable Mock object with side_effect set to a list of faked
120+
aggregates
121+
"""
122+
if aggregates is None:
123+
aggregates = FakeAggregate.create_aggregates(count)
124+
return mock.MagicMock(side_effect=aggregates)
125+
90126

91127
class FakeComputev2Client(object):
92128

@@ -732,7 +768,7 @@ def get_flavors(flavors=None, count=2):
732768
flavors
733769
"""
734770
if flavors is None:
735-
flavors = FakeServer.create_flavors(count)
771+
flavors = FakeFlavor.create_flavors(count)
736772
return mock.MagicMock(side_effect=flavors)
737773

738774

openstackclient/tests/compute/v2/test_aggregate.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
# under the License.
1414
#
1515

16+
import mock
17+
from mock import call
18+
19+
from osc_lib import exceptions
20+
from osc_lib import utils
21+
1622
from openstackclient.compute.v2 import aggregate
1723
from openstackclient.tests.compute.v2 import fakes as compute_fakes
1824
from openstackclient.tests import utils as tests_utils
@@ -135,25 +141,74 @@ def test_aggregate_create_with_property(self):
135141

136142
class TestAggregateDelete(TestAggregate):
137143

144+
fake_ags = compute_fakes.FakeAggregate.create_aggregates(count=2)
145+
138146
def setUp(self):
139147
super(TestAggregateDelete, self).setUp()
140148

141-
self.aggregate_mock.get.return_value = self.fake_ag
149+
self.aggregate_mock.get = (
150+
compute_fakes.FakeAggregate.get_aggregates(self.fake_ags))
142151
self.cmd = aggregate.DeleteAggregate(self.app, None)
143152

144153
def test_aggregate_delete(self):
145154
arglist = [
146-
'ag1',
155+
self.fake_ags[0].id
147156
]
148157
verifylist = [
149-
('aggregate', 'ag1'),
158+
('aggregate', [self.fake_ags[0].id]),
150159
]
151160
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
152161
result = self.cmd.take_action(parsed_args)
153-
self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
154-
self.aggregate_mock.delete.assert_called_once_with(self.fake_ag.id)
162+
self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id)
163+
self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id)
155164
self.assertIsNone(result)
156165

166+
def test_delete_multiple_aggregates(self):
167+
arglist = []
168+
for a in self.fake_ags:
169+
arglist.append(a.id)
170+
verifylist = [
171+
('aggregate', arglist),
172+
]
173+
174+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
175+
result = self.cmd.take_action(parsed_args)
176+
177+
calls = []
178+
for a in self.fake_ags:
179+
calls.append(call(a.id))
180+
self.aggregate_mock.delete.assert_has_calls(calls)
181+
self.assertIsNone(result)
182+
183+
def test_delete_multiple_agggregates_with_exception(self):
184+
arglist = [
185+
self.fake_ags[0].id,
186+
'unexist_aggregate',
187+
]
188+
verifylist = [
189+
('aggregate', arglist),
190+
]
191+
192+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
193+
194+
find_mock_result = [self.fake_ags[0], exceptions.CommandError]
195+
with mock.patch.object(utils, 'find_resource',
196+
side_effect=find_mock_result) as find_mock:
197+
try:
198+
self.cmd.take_action(parsed_args)
199+
self.fail('CommandError should be raised.')
200+
except exceptions.CommandError as e:
201+
self.assertEqual('1 of 2 aggregates failed to delete.',
202+
str(e))
203+
204+
find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id)
205+
find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate')
206+
207+
self.assertEqual(2, find_mock.call_count)
208+
self.aggregate_mock.delete.assert_called_once_with(
209+
self.fake_ags[0].id
210+
)
211+
157212

158213
class TestAggregateList(TestAggregate):
159214

0 commit comments

Comments
 (0)