diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/api/auth.py | 5 | ||||
| -rw-r--r-- | openstackclient/common/command.py | 5 | ||||
| -rw-r--r-- | openstackclient/common/exceptions.py | 5 | ||||
| -rw-r--r-- | openstackclient/common/logs.py | 5 | ||||
| -rw-r--r-- | openstackclient/common/parseractions.py | 5 | ||||
| -rw-r--r-- | openstackclient/common/timing.py | 5 | ||||
| -rw-r--r-- | openstackclient/common/utils.py | 5 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/v1/fakes.py | 156 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/v1/test_qos_specs.py | 77 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/v1/test_volume.py | 144 | ||||
| -rw-r--r-- | openstackclient/tests/unit/volume/v2/test_volume.py | 71 | ||||
| -rw-r--r-- | openstackclient/volume/v1/backup.py | 26 | ||||
| -rw-r--r-- | openstackclient/volume/v1/qos_specs.py | 26 | ||||
| -rw-r--r-- | openstackclient/volume/v1/snapshot.py | 22 | ||||
| -rw-r--r-- | openstackclient/volume/v1/volume.py | 41 | ||||
| -rw-r--r-- | openstackclient/volume/v2/volume.py | 18 |
16 files changed, 574 insertions, 42 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index d62a82dc..7c520f49 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.api.auth import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.api.auth\n" % __name__ + "Please use osc_lib.api.auth. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 29c1534d..44954da3 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -15,12 +15,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.command.command import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.command\n" % __name__ + "Please use osc_lib.command.command. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 7124074c..ed497e7b 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.exceptions import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.exceptions\n" % __name__ + "Please use osc_lib.exceptions. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 8aa97d5b..24bf07eb 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -14,13 +14,16 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.logs import * # noqa from osc_lib.logs import _FileFormatter # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.logs\n" % __name__ + "Please use osc_lib.logs. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index fa5148ec..3af3a017 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.cli.parseractions import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.cli.parseractions\n" % __name__ + "Please use osc_lib.cli.parseractions. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index facbec35..444f0cb2 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.command.timing import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.timing\n" % __name__ + "Please use osc_lib.command.timing. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 73cd3dc9..aeb3aea7 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.utils import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.utils\n" % __name__ + "Please use osc_lib.utils. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index c6fee7d1..5c1f3b12 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -13,7 +13,10 @@ # under the License. # +import copy import mock +import random +import uuid from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -234,6 +237,159 @@ class FakeService(object): return mock.MagicMock(side_effect=services) +class FakeQos(object): + """Fake one or more Qos specification.""" + + @staticmethod + def create_one_qos(attrs=None): + """Create a fake Qos specification. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, consumer, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_info = { + "id": 'qos-id-' + uuid.uuid4().hex, + "name": 'qos-name-' + uuid.uuid4().hex, + "consumer": 'front-end', + "specs": {"foo": "bar", "iops": "9001"}, + } + + # Overwrite default attributes. + qos_info.update(attrs) + + qos = fakes.FakeResource( + info=copy.deepcopy(qos_info), + loaded=True) + return qos + + @staticmethod + def create_qoses(attrs=None, count=2): + """Create multiple fake Qos specifications. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of Qos specifications to fake + :return: + A list of FakeResource objects faking the Qos specifications + """ + qoses = [] + for i in range(0, count): + qos = FakeQos.create_one_qos(attrs) + qoses.append(qos) + + return qoses + + @staticmethod + def get_qoses(qoses=None, count=2): + """Get an iterable MagicMock object with a list of faked qoses. + + If qoses list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking qoses + :param Integer count: + The number of qoses to be faked + :return + An iterable Mock object with side_effect set to a list of faked + qoses + """ + if qoses is None: + qoses = FakeQos.create_qoses(count) + + return mock.MagicMock(side_effect=qoses) + + +class FakeVolume(object): + """Fake one or more volumes.""" + + @staticmethod + def create_one_volume(attrs=None): + """Create a fake volume. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :return: + A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + volume_info = { + 'id': 'volume-id' + uuid.uuid4().hex, + 'name': 'volume-name' + uuid.uuid4().hex, + 'description': 'description' + uuid.uuid4().hex, + 'status': random.choice(['available', 'in_use']), + 'size': random.randint(1, 20), + 'volume_type': + random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'bootable': + random.randint(0, 1), + 'metadata': { + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, + 'snapshot_id': random.randint(1, 5), + 'availability_zone': 'zone' + uuid.uuid4().hex, + 'attachments': [{ + 'device': '/dev/' + uuid.uuid4().hex, + 'server_id': uuid.uuid4().hex, + }, ], + } + + # Overwrite default attributes if there are some attributes set + volume_info.update(attrs) + + volume = fakes.FakeResource( + None, + volume_info, + loaded=True) + return volume + + @staticmethod + def create_volumes(attrs=None, count=2): + """Create multiple fake volumes. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :param Integer count: + The number of volumes to be faked + :return: + A list of FakeResource objects + """ + volumes = [] + for n in range(0, count): + volumes.append(FakeVolume.create_one_volume(attrs)) + + return volumes + + @staticmethod + def get_volumes(volumes=None, count=2): + """Get an iterable MagicMock object with a list of faked volumes. + + If volumes list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking volumes + :param Integer count: + The number of volumes to be faked + :return + An iterable Mock object with side_effect set to a list of faked + volumes + """ + if volumes is None: + volumes = FakeVolume.create_volumes(count) + + return mock.MagicMock(side_effect=volumes) + + class FakeImagev1Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 7b87ccb3..81680ab4 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -14,7 +14,10 @@ # import copy +import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit import fakes @@ -188,62 +191,106 @@ class TestQosCreate(TestQos): class TestQosDelete(TestQos): + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) + def setUp(self): super(TestQosDelete, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True, - ) - + self.qos_mock.get = ( + volume_fakes.FakeQos.get_qoses(self.qos_specs)) # Get the command object to test self.cmd = qos_specs.DeleteQos(self.app, None) def test_qos_delete_with_id(self): arglist = [ - volume_fakes.qos_id + self.qos_specs[0].id ] verifylist = [ - ('qos_specs', [volume_fakes.qos_id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_name(self): arglist = [ - volume_fakes.qos_name + self.qos_specs[0].name ] verifylist = [ - ('qos_specs', [volume_fakes.qos_name]) + ('qos_specs', [self.qos_specs[0].name]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_force(self): arglist = [ '--force', - volume_fakes.qos_id + self.qos_specs[0].id ] verifylist = [ ('force', True), - ('qos_specs', [volume_fakes.qos_id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, True) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, True) self.assertIsNone(result) + def test_delete_multiple_qoses(self): + arglist = [] + for q in self.qos_specs: + arglist.append(q.id) + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for q in self.qos_specs: + calls.append(call(q.id, False)) + self.qos_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_qoses_with_exception(self): + arglist = [ + self.qos_specs[0].id, + 'unexist_qos', + ] + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.qos_specs[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 QoS specifications failed to delete.', str(e)) + + find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id) + find_mock.assert_any_call(self.qos_mock, 'unexist_qos') + + self.assertEqual(2, find_mock.call_count) + self.qos_mock.delete.assert_called_once_with( + self.qos_specs[0].id, False + ) + class TestQosDisassociate(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index f90566fd..54ec9e7e 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -13,8 +13,13 @@ # under the License. # +import argparse import copy import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -43,6 +48,14 @@ class TestVolume(volume_fakes.TestVolumev1): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + def setup_volumes_mock(self, count): + volumes = volume_fakes.FakeVolume.create_volumes(count=count) + + self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes( + volumes, + 0) + return volumes + # TODO(dtroyer): The volume create tests are incomplete, only the minimal # options and the options that require additional processing @@ -397,6 +410,97 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.datalist, data) +class TestVolumeDelete(TestVolume): + + def setUp(self): + super(TestVolumeDelete, self).setUp() + + self.volumes_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume.DeleteVolume(self.app, None) + + def test_volume_delete_one_volume(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + volumes[0].id + ] + verifylist = [ + ("force", False), + ("volumes", [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.delete.assert_called_once_with(volumes[0].id) + self.assertIsNone(result) + + def test_volume_delete_multi_volumes(self): + volumes = self.setup_volumes_mock(count=3) + + arglist = [v.id for v in volumes] + verifylist = [ + ('force', False), + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [call(v.id) for v in volumes] + self.volumes_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_volume_delete_multi_volumes_with_exception(self): + volumes = self.setup_volumes_mock(count=2) + + arglist = [ + volumes[0].id, + 'unexist_volume', + ] + verifylist = [ + ('force', False), + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [volumes[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volumes failed to delete.', + str(e)) + + find_mock.assert_any_call(self.volumes_mock, volumes[0].id) + find_mock.assert_any_call(self.volumes_mock, 'unexist_volume') + + self.assertEqual(2, find_mock.call_count) + self.volumes_mock.delete.assert_called_once_with(volumes[0].id) + + def test_volume_delete_with_force(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + '--force', + volumes[0].id, + ] + verifylist = [ + ('force', True), + ('volumes', [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.force_delete.assert_called_once_with(volumes[0].id) + self.assertIsNone(result) + + class TestVolumeList(TestVolume): columns = ( @@ -437,6 +541,7 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -454,6 +559,7 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', volume_fakes.volume_name), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -470,6 +576,7 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', volume_fakes.volume_status), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -486,6 +593,7 @@ class TestVolumeList(TestVolume): ('all_projects', True), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -502,6 +610,7 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -532,6 +641,41 @@ class TestVolumeList(TestVolume): ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_with_limit(self): + arglist = [ + '--limit', '2', + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.list.assert_called_once_with( + limit=2, + search_opts={ + 'status': None, + 'display_name': None, + 'all_tenants': False, } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_volume_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeSet(TestVolume): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 66f8f74d..de059b1b 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -12,6 +12,7 @@ # under the License. # +import argparse import mock from mock import call @@ -553,6 +554,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -581,6 +584,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -611,6 +616,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -639,6 +646,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -668,6 +677,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -696,6 +707,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', self.mock_volume.name), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -724,6 +737,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', self.mock_volume.status), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -752,6 +767,8 @@ class TestVolumeList(TestVolume): ('all_projects', True), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -780,6 +797,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -813,6 +832,58 @@ class TestVolumeList(TestVolume): ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_with_marker_and_limit(self): + arglist = [ + "--marker", self.mock_volume.id, + "--limit", "2", + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('marker', self.mock_volume.id), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, + msg, + ), ) + + self.volumes_mock.list.assert_called_once_with( + marker=self.mock_volume.id, + limit=2, + search_opts={ + 'status': None, + 'project_id': None, + 'user_id': None, + 'display_name': None, + 'all_tenants': False, } + ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeSet(TestVolume): diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 539ed369..c9d0ca0d 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -19,12 +19,16 @@ import copy import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateVolumeBackup(command.ShowOne): """Create new volume backup""" @@ -100,10 +104,24 @@ class DeleteVolumeBackup(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for backup in parsed_args.backups: - backup_id = utils.find_resource(volume_client.backups, - backup).id - volume_client.backups.delete(backup_id) + result = 0 + + for i in parsed_args.backups: + try: + backup_id = utils.find_resource( + volume_client.backups, i).id + volume_client.backups.delete(backup_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete backup with " + "name or ID '%(backup)s': %(e)s"), + {'backup': i, 'e': e}) + + if result > 0: + total = len(parsed_args.backups) + msg = (_("%(result)s of %(total)s backups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DeleteBackup(DeleteVolumeBackup): diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index c5850871..b982c0e6 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -15,14 +15,20 @@ """Volume v1 QoS action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class AssociateQos(command.Command): """Associate a QoS specification to a volume type""" @@ -113,9 +119,23 @@ class DeleteQos(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for qos in parsed_args.qos_specs: - qos_spec = utils.find_resource(volume_client.qos_specs, qos) - volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + result = 0 + + for i in parsed_args.qos_specs: + try: + qos_spec = utils.find_resource(volume_client.qos_specs, i) + volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete QoS specification with " + "name or ID '%(qos)s': %(e)s"), + {'qos': i, 'e': e}) + + if result > 0: + total = len(parsed_args.qos_specs) + msg = (_("%(result)s of %(total)s QoS specifications failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DisassociateQos(command.Command): diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index f8fa7c95..c4d113a3 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -93,10 +93,24 @@ class DeleteSnapshot(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for snapshot in parsed_args.snapshots: - snapshot_id = utils.find_resource(volume_client.volume_snapshots, - snapshot).id - volume_client.volume_snapshots.delete(snapshot_id) + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s"), + {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSnapshot(command.Lister): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 820673bb..16f0d6db 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -20,6 +20,7 @@ import logging from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -184,13 +185,27 @@ class DeleteVolume(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for volume in parsed_args.volumes: - volume_obj = utils.find_resource( - volume_client.volumes, volume) - if parsed_args.force: - volume_client.volumes.force_delete(volume_obj.id) - else: - volume_client.volumes.delete(volume_obj.id) + result = 0 + + for i in parsed_args.volumes: + try: + volume_obj = utils.find_resource( + volume_client.volumes, i) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume with " + "name or ID '%(volume)s': %(e)s"), + {'volume': i, 'e': e}) + + if result > 0: + total = len(parsed_args.volumes) + msg = (_("%(result)s of %(total)s volumes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolume(command.Lister): @@ -220,6 +235,13 @@ class ListVolume(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='<limit>', + help=_('Maximum number of volumes to display'), + ) return parser def take_action(self, parsed_args): @@ -295,7 +317,10 @@ class ListVolume(command.Lister): 'status': parsed_args.status, } - data = volume_client.volumes.list(search_opts=search_opts) + data = volume_client.volumes.list( + search_opts=search_opts, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index bd201e00..28946a5f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -246,6 +246,18 @@ class ListVolume(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--marker', + metavar='<marker>', + help=_('The last volume ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='<limit>', + help=_('Maximum number of volumes to display'), + ) return parser def take_action(self, parsed_args): @@ -328,7 +340,11 @@ class ListVolume(command.Lister): 'status': parsed_args.status, } - data = volume_client.volumes.list(search_opts=search_opts) + data = volume_client.volumes.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( |
