Skip to content

Commit af81a92

Browse files
author
Huanxuan Ao
committed
Support error handling for delete commands in volume v1
Some delete commands in volume v1 support multi delete but do not support error handling, this patch fixes them, and this patch also refactor (or add new) unit tests for some delete commands in volume v1. Change-Id: Ia8177698f8733cfe75ea0ff00eee8fdc0820f62e
1 parent 676a0e9 commit af81a92

7 files changed

Lines changed: 411 additions & 33 deletions

File tree

openstackclient/tests/unit/volume/v1/fakes.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
# under the License.
1414
#
1515

16+
import copy
1617
import mock
18+
import random
19+
import uuid
1720

1821
from openstackclient.tests.unit import fakes
1922
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
@@ -234,6 +237,159 @@ def get_services(services=None, count=2):
234237
return mock.MagicMock(side_effect=services)
235238

236239

240+
class FakeQos(object):
241+
"""Fake one or more Qos specification."""
242+
243+
@staticmethod
244+
def create_one_qos(attrs=None):
245+
"""Create a fake Qos specification.
246+
247+
:param Dictionary attrs:
248+
A dictionary with all attributes
249+
:return:
250+
A FakeResource object with id, name, consumer, etc.
251+
"""
252+
attrs = attrs or {}
253+
254+
# Set default attributes.
255+
qos_info = {
256+
"id": 'qos-id-' + uuid.uuid4().hex,
257+
"name": 'qos-name-' + uuid.uuid4().hex,
258+
"consumer": 'front-end',
259+
"specs": {"foo": "bar", "iops": "9001"},
260+
}
261+
262+
# Overwrite default attributes.
263+
qos_info.update(attrs)
264+
265+
qos = fakes.FakeResource(
266+
info=copy.deepcopy(qos_info),
267+
loaded=True)
268+
return qos
269+
270+
@staticmethod
271+
def create_qoses(attrs=None, count=2):
272+
"""Create multiple fake Qos specifications.
273+
274+
:param Dictionary attrs:
275+
A dictionary with all attributes
276+
:param int count:
277+
The number of Qos specifications to fake
278+
:return:
279+
A list of FakeResource objects faking the Qos specifications
280+
"""
281+
qoses = []
282+
for i in range(0, count):
283+
qos = FakeQos.create_one_qos(attrs)
284+
qoses.append(qos)
285+
286+
return qoses
287+
288+
@staticmethod
289+
def get_qoses(qoses=None, count=2):
290+
"""Get an iterable MagicMock object with a list of faked qoses.
291+
292+
If qoses list is provided, then initialize the Mock object with the
293+
list. Otherwise create one.
294+
295+
:param List volumes:
296+
A list of FakeResource objects faking qoses
297+
:param Integer count:
298+
The number of qoses to be faked
299+
:return
300+
An iterable Mock object with side_effect set to a list of faked
301+
qoses
302+
"""
303+
if qoses is None:
304+
qoses = FakeQos.create_qoses(count)
305+
306+
return mock.MagicMock(side_effect=qoses)
307+
308+
309+
class FakeVolume(object):
310+
"""Fake one or more volumes."""
311+
312+
@staticmethod
313+
def create_one_volume(attrs=None):
314+
"""Create a fake volume.
315+
316+
:param Dictionary attrs:
317+
A dictionary with all attributes of volume
318+
:return:
319+
A FakeResource object with id, name, status, etc.
320+
"""
321+
attrs = attrs or {}
322+
323+
# Set default attribute
324+
volume_info = {
325+
'id': 'volume-id' + uuid.uuid4().hex,
326+
'name': 'volume-name' + uuid.uuid4().hex,
327+
'description': 'description' + uuid.uuid4().hex,
328+
'status': random.choice(['available', 'in_use']),
329+
'size': random.randint(1, 20),
330+
'volume_type':
331+
random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']),
332+
'bootable':
333+
random.randint(0, 1),
334+
'metadata': {
335+
'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex,
336+
'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex,
337+
'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex},
338+
'snapshot_id': random.randint(1, 5),
339+
'availability_zone': 'zone' + uuid.uuid4().hex,
340+
'attachments': [{
341+
'device': '/dev/' + uuid.uuid4().hex,
342+
'server_id': uuid.uuid4().hex,
343+
}, ],
344+
}
345+
346+
# Overwrite default attributes if there are some attributes set
347+
volume_info.update(attrs)
348+
349+
volume = fakes.FakeResource(
350+
None,
351+
volume_info,
352+
loaded=True)
353+
return volume
354+
355+
@staticmethod
356+
def create_volumes(attrs=None, count=2):
357+
"""Create multiple fake volumes.
358+
359+
:param Dictionary attrs:
360+
A dictionary with all attributes of volume
361+
:param Integer count:
362+
The number of volumes to be faked
363+
:return:
364+
A list of FakeResource objects
365+
"""
366+
volumes = []
367+
for n in range(0, count):
368+
volumes.append(FakeVolume.create_one_volume(attrs))
369+
370+
return volumes
371+
372+
@staticmethod
373+
def get_volumes(volumes=None, count=2):
374+
"""Get an iterable MagicMock object with a list of faked volumes.
375+
376+
If volumes list is provided, then initialize the Mock object with the
377+
list. Otherwise create one.
378+
379+
:param List volumes:
380+
A list of FakeResource objects faking volumes
381+
:param Integer count:
382+
The number of volumes to be faked
383+
:return
384+
An iterable Mock object with side_effect set to a list of faked
385+
volumes
386+
"""
387+
if volumes is None:
388+
volumes = FakeVolume.create_volumes(count)
389+
390+
return mock.MagicMock(side_effect=volumes)
391+
392+
237393
class FakeImagev1Client(object):
238394

239395
def __init__(self, **kwargs):

openstackclient/tests/unit/volume/v1/test_qos_specs.py

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

1616
import copy
17+
import mock
18+
from mock import call
1719

20+
from osc_lib import exceptions
1821
from osc_lib import utils
1922

2023
from openstackclient.tests.unit import fakes
@@ -188,62 +191,106 @@ def test_qos_create_with_properties(self):
188191

189192
class TestQosDelete(TestQos):
190193

194+
qos_specs = volume_fakes.FakeQos.create_qoses(count=2)
195+
191196
def setUp(self):
192197
super(TestQosDelete, self).setUp()
193198

194-
self.qos_mock.get.return_value = fakes.FakeResource(
195-
None,
196-
copy.deepcopy(volume_fakes.QOS),
197-
loaded=True,
198-
)
199-
199+
self.qos_mock.get = (
200+
volume_fakes.FakeQos.get_qoses(self.qos_specs))
200201
# Get the command object to test
201202
self.cmd = qos_specs.DeleteQos(self.app, None)
202203

203204
def test_qos_delete_with_id(self):
204205
arglist = [
205-
volume_fakes.qos_id
206+
self.qos_specs[0].id
206207
]
207208
verifylist = [
208-
('qos_specs', [volume_fakes.qos_id])
209+
('qos_specs', [self.qos_specs[0].id])
209210
]
210211
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
211212

212213
result = self.cmd.take_action(parsed_args)
213214

214-
self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False)
215+
self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False)
215216
self.assertIsNone(result)
216217

217218
def test_qos_delete_with_name(self):
218219
arglist = [
219-
volume_fakes.qos_name
220+
self.qos_specs[0].name
220221
]
221222
verifylist = [
222-
('qos_specs', [volume_fakes.qos_name])
223+
('qos_specs', [self.qos_specs[0].name])
223224
]
224225
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
225226

226227
result = self.cmd.take_action(parsed_args)
227228

228-
self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False)
229+
self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False)
229230
self.assertIsNone(result)
230231

231232
def test_qos_delete_with_force(self):
232233
arglist = [
233234
'--force',
234-
volume_fakes.qos_id
235+
self.qos_specs[0].id
235236
]
236237
verifylist = [
237238
('force', True),
238-
('qos_specs', [volume_fakes.qos_id])
239+
('qos_specs', [self.qos_specs[0].id])
239240
]
240241
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
241242

242243
result = self.cmd.take_action(parsed_args)
243244

244-
self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, True)
245+
self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, True)
245246
self.assertIsNone(result)
246247

248+
def test_delete_multiple_qoses(self):
249+
arglist = []
250+
for q in self.qos_specs:
251+
arglist.append(q.id)
252+
verifylist = [
253+
('qos_specs', arglist),
254+
]
255+
256+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
257+
result = self.cmd.take_action(parsed_args)
258+
259+
calls = []
260+
for q in self.qos_specs:
261+
calls.append(call(q.id, False))
262+
self.qos_mock.delete.assert_has_calls(calls)
263+
self.assertIsNone(result)
264+
265+
def test_delete_multiple_qoses_with_exception(self):
266+
arglist = [
267+
self.qos_specs[0].id,
268+
'unexist_qos',
269+
]
270+
verifylist = [
271+
('qos_specs', arglist),
272+
]
273+
274+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
275+
276+
find_mock_result = [self.qos_specs[0], exceptions.CommandError]
277+
with mock.patch.object(utils, 'find_resource',
278+
side_effect=find_mock_result) as find_mock:
279+
try:
280+
self.cmd.take_action(parsed_args)
281+
self.fail('CommandError should be raised.')
282+
except exceptions.CommandError as e:
283+
self.assertEqual(
284+
'1 of 2 QoS specifications failed to delete.', str(e))
285+
286+
find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id)
287+
find_mock.assert_any_call(self.qos_mock, 'unexist_qos')
288+
289+
self.assertEqual(2, find_mock.call_count)
290+
self.qos_mock.delete.assert_called_once_with(
291+
self.qos_specs[0].id, False
292+
)
293+
247294

248295
class TestQosDisassociate(TestQos):
249296

0 commit comments

Comments
 (0)